Skip to content

Commit

Permalink
v2.9.2 cherry-picks (#12088)
Browse files Browse the repository at this point in the history
* Fallback to hardcoded units in browsers without `NumberFormat` support in `ScaleControl` (#12068)

Add a workaround in ScaleControl to support localization in browsers without NumberFormat support

Co-authored-by: Ansis Brammanis <[email protected]>

* Fix GeolocateControl in Safari (#12080)

* fix GeolocateControl in Safari

* update geolocate unit test to cover the fix

Co-authored-by: Stepan Kuzmin <[email protected]>
Co-authored-by: Ansis Brammanis <[email protected]>
Co-authored-by: Volodymyr Agafonkin <[email protected]>
  • Loading branch information
4 people authored Jul 14, 2022
1 parent b13b0ae commit b912966
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 46 deletions.
2 changes: 1 addition & 1 deletion src/ui/control/geolocate_control.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ class GeolocateControl extends Evented {
callback(this._supportsGeolocation);
});
} else {
this._supportsGeolocation = !!this.geolocation;
this._supportsGeolocation = !!this.options.geolocation;
callback(this._supportsGeolocation);
}
}
Expand Down
121 changes: 77 additions & 44 deletions src/ui/control/scale_control.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,16 @@ class ScaleControl {
constructor(options: Options) {
this.options = extend({}, defaultOptions, options);

// Some old browsers (e.g., Safari < 14.1) don't support the "unit" style.
// This is a workaround to display the scale without proper internationalization support.
if (!isNumberFormatSupported()) {
// $FlowIgnore[cannot-write]
this._setScale = legacySetScale.bind(this);
}

bindAll([
'_update',
'_setScale',
'setUnit'
], this);
}
Expand All @@ -54,7 +62,56 @@ class ScaleControl {
}

_update() {
updateScale(this._map, this._container, this._language, this.options);
// A horizontal scale is imagined to be present at center of the map
// container with maximum length (Default) as 100px.
// Using spherical law of cosines approximation, the real distance is
// found between the two coordinates.
const maxWidth = this.options.maxWidth || 100;

const map = this._map;
const y = map._containerHeight / 2;
const x = (map._containerWidth / 2) - maxWidth / 2;
const left = map.unproject([x, y]);
const right = map.unproject([x + maxWidth, y]);
const maxMeters = left.distanceTo(right);
// The real distance corresponding to 100px scale length is rounded off to
// near pretty number and the scale length for the same is found out.
// Default unit of the scale is based on User's locale.
if (this.options.unit === 'imperial') {
const maxFeet = 3.2808 * maxMeters;
if (maxFeet > 5280) {
const maxMiles = maxFeet / 5280;
this._setScale(maxWidth, maxMiles, 'mile');
} else {
this._setScale(maxWidth, maxFeet, 'foot');
}
} else if (this.options.unit === 'nautical') {
const maxNauticals = maxMeters / 1852;
this._setScale(maxWidth, maxNauticals, 'nautical-mile');
} else if (maxMeters >= 1000) {
this._setScale(maxWidth, maxMeters / 1000, 'kilometer');
} else {
this._setScale(maxWidth, maxMeters, 'meter');
}
}

_setScale(maxWidth: number, maxDistance: number, unit: string) {
const distance = getRoundNum(maxDistance);
const ratio = distance / maxDistance;

this._map._requestDomTask(() => {
this._container.style.width = `${maxWidth * ratio}px`;

// Intl.NumberFormat doesn't support nautical-mile as a unit,
// so we are hardcoding `nm` as a unit symbol for all locales
if (unit === 'nautical-mile') {
this._container.innerHTML = `${distance}&nbsp;nm`;
return;
}

// $FlowFixMe — flow v0.142.0 doesn't support optional `locales` argument and `unit` style option
this._container.innerHTML = new Intl.NumberFormat(this._language, {style: 'unit', unitDisplay: 'narrow', unit}).format(distance);
});
}

onAdd(map: Map): HTMLElement {
Expand Down Expand Up @@ -93,55 +150,31 @@ class ScaleControl {

export default ScaleControl;

function updateScale(map, container, language, options) {
// A horizontal scale is imagined to be present at center of the map
// container with maximum length (Default) as 100px.
// Using spherical law of cosines approximation, the real distance is
// found between the two coordinates.
const maxWidth = (options && options.maxWidth) || 100;

const y = map._containerHeight / 2;
const x = (map._containerWidth / 2) - maxWidth / 2;
const left = map.unproject([x, y]);
const right = map.unproject([x + maxWidth, y]);
const maxMeters = left.distanceTo(right);
// The real distance corresponding to 100px scale length is rounded off to
// near pretty number and the scale length for the same is found out.
// Default unit of the scale is based on User's locale.
if (options && options.unit === 'imperial') {
const maxFeet = 3.2808 * maxMeters;
if (maxFeet > 5280) {
const maxMiles = maxFeet / 5280;
setScale(container, maxWidth, maxMiles, language, 'mile', map);
} else {
setScale(container, maxWidth, maxFeet, language, 'foot', map);
}
} else if (options && options.unit === 'nautical') {
const maxNauticals = maxMeters / 1852;
setScale(container, maxWidth, maxNauticals, language, 'nautical-mile', map);
} else if (maxMeters >= 1000) {
setScale(container, maxWidth, maxMeters / 1000, language, 'kilometer', map);
} else {
setScale(container, maxWidth, maxMeters, language, 'meter', map);
function isNumberFormatSupported() {
try {
// $FlowIgnore
new Intl.NumberFormat('en', {style: 'unit', unitDisplay: 'narrow', unit: 'meter'});
return true;
} catch (_) {
return false;
}
}

function setScale(container, maxWidth, maxDistance, language, unit, map) {
function legacySetScale(maxWidth: number, maxDistance: number, unit: string) {
const distance = getRoundNum(maxDistance);
const ratio = distance / maxDistance;

map._requestDomTask(() => {
container.style.width = `${maxWidth * ratio}px`;

// Intl.NumberFormat doesn't support nautical-mile as a unit,
// so we are hardcoding `nm` as a unit symbol for all locales
if (unit === 'nautical-mile') {
container.innerHTML = `${distance}&nbsp;nm`;
return;
}

// $FlowFixMe — flow v0.142.0 doesn't support optional `locales` argument and `unit` style option
container.innerHTML = new Intl.NumberFormat(language, {style: 'unit', unitDisplay: 'narrow', unit}).format(distance);
const unitAbbr = {
kilometer: 'km',
meter: 'm',
mile: 'mi',
foot: 'ft',
'nautical-mile': 'nm',
}[unit];

this._map._requestDomTask(() => {
this._container.style.width = `${maxWidth * ratio}px`;
this._container.innerHTML = `${distance}&nbsp;${unitAbbr}`;
});
}

Expand Down
5 changes: 4 additions & 1 deletion test/unit/ui/control/geolocate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@ function lngLatAsFixed(lngLat, digits) {

test('GeolocateControl with no options', (t) => {
const map = createMap(t);
t.plan(0);
t.plan(1);

const geolocate = new GeolocateControl();
map.addControl(geolocate);

t.equal(geolocate._geolocateButton.disabled, false);

t.end();
});

Expand Down
24 changes: 24 additions & 0 deletions test/unit/ui/control/scale.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,27 @@ test('ScaleControl should support different projections', (t) => {

t.end();
});

test('ScaleControl should work in legacy safari', (t) => {
const realNumberFormat = Intl.NumberFormat;
Intl.NumberFormat = function(arg, options) {
if (options && options.style === 'unit') {
throw new Error('not supported');
}
return realNumberFormat.call(Intl, arg, options);
};
try {
const map = createMap(t);
const scale = new ScaleControl();
const selector = '.mapboxgl-ctrl-bottom-left .mapboxgl-ctrl-scale';
map.addControl(scale);
map._domRenderTaskQueue.run();

const contents = map.getContainer().querySelector(selector).innerHTML;
t.match(contents, /km/);
} finally {
Intl.NumberFormat = realNumberFormat;
}
t.end();

});

0 comments on commit b912966

Please sign in to comment.