Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow overriding of GeoJSON output #6681

Closed
wants to merge 1 commit into from
Closed

Allow overriding of GeoJSON output #6681

wants to merge 1 commit into from

Conversation

inkstak
Copy link
Contributor

@inkstak inkstak commented May 29, 2019

Background:

I reported in #6586 a problem about accuracy in L.Util.formatNum rounding method.
Following my pull request, a performance issue has been reported in #6668, and my changes have been reverted.

Problem:

I agree with the arguments advanced in this last thread.
In the end, I especially have a problem with the inaccuracy of the toGeoJson function.

The solution for me was to overwrite the GeoJSON.latLngToCoords but it's not so easy because Leaflet uses internal API.
If I overwrite this function, it won't be used by GeoJSON.latLngsToCoords.
If I also overwrite this function, it won't be used by every toGeoJSON declared for each feature.
Et cetera. At the end, I have to copy so many functions.

Solution:

I just replaced latLngToCoords with GeoJSON.latLngToCoords

EDIT : the first PR text was submitted by error.

@inkstak
Copy link
Contributor Author

inkstak commented May 29, 2019

This request intends to facilitate overwriting of toGeoJSON output.

An other approach to my main problem of accuracy may be that toGeoJSON skip numbers formatting when not precision (or a specific value) is provided. This is the approach I used in my monkey-patch for now (see the spec attached).

Comment on lines +477 to +512
var marker = new L.Marker([-1.4837191022531273, 43.49222084042808]),
layerGroup = new L.LayerGroup([marker]);

expect(layerGroup.toGeoJSON()).to.eql({
type: 'FeatureCollection',
features: [{
type: 'Feature',
properties: {},
geometry: {
type: 'Point',
coordinates: [43.492221, -1.483719]
}
}]
});

L.GeoJSON.latLngToCoords = function (latlng, precision) {
if (typeof precision === 'number') {
return latlng.alt !== undefined ?
[L.Util.formatNum(latlng.lng, precision), L.Util.formatNum(latlng.lat, precision), L.Util.formatNum(latlng.alt, precision)] :
[L.Util.formatNum(latlng.lng, precision), L.Util.formatNum(latlng.lat, precision)];
} else {
return latlng.alt !== undefined ? [latlng.lng, latlng.lat, latlng.alt] : [latlng.lng, latlng.lat];
}
};

expect(layerGroup.toGeoJSON()).to.eql({
type: 'FeatureCollection',
features: [{
type: 'Feature',
properties: {},
geometry: {
type: 'Point',
coordinates: [43.49222084042808, -1.4837191022531273]
}
}]
});
Copy link
Collaborator

@johnd0e johnd0e Apr 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var marker = new L.Marker([-1.4837191022531273, 43.49222084042808]),
layerGroup = new L.LayerGroup([marker]);
expect(layerGroup.toGeoJSON()).to.eql({
type: 'FeatureCollection',
features: [{
type: 'Feature',
properties: {},
geometry: {
type: 'Point',
coordinates: [43.492221, -1.483719]
}
}]
});
L.GeoJSON.latLngToCoords = function (latlng, precision) {
if (typeof precision === 'number') {
return latlng.alt !== undefined ?
[L.Util.formatNum(latlng.lng, precision), L.Util.formatNum(latlng.lat, precision), L.Util.formatNum(latlng.alt, precision)] :
[L.Util.formatNum(latlng.lng, precision), L.Util.formatNum(latlng.lat, precision)];
} else {
return latlng.alt !== undefined ? [latlng.lng, latlng.lat, latlng.alt] : [latlng.lng, latlng.lat];
}
};
expect(layerGroup.toGeoJSON()).to.eql({
type: 'FeatureCollection',
features: [{
type: 'Feature',
properties: {},
geometry: {
type: 'Point',
coordinates: [43.49222084042808, -1.4837191022531273]
}
}]
});
var arrLatLng = [-1.4837191022531273, 43.49222084042808];
var arrLngLat = [arrLatLng[1], arrLatLng[0]];
var latLng = L.latLng(arrLatLng);
var latLngToCoords = L.GeoJSON.latLngToCoords; // keep ref to original func
// override with custom function, to prevent latlngs formatting when precision is not specified
// (to avoid round-off errors)
L.GeoJSON.latLngToCoords = function (latlng, precision) {
if (typeof precision === 'number') {
return latLngToCoords.apply(this, arguments);
}
return latlng.alt !== undefined ? [latlng.lng, latlng.lat, latlng.alt] : [latlng.lng, latlng.lat];
};
expect(L.GeoJSON.latLngToCoords(latLng, 6)).to.eql([43.492221, -1.483719]);
expect(L.GeoJSON.latLngToCoords(latLng)).to.eql(arrLngLat);
var marker = new L.Marker(latLng);
expect(marker.toGeoJSON()).to.eql({
type: 'Feature',
properties: {},
geometry: {
type: 'Point',
coordinates: arrLngLat
}
});
L.GeoJSON.latLngToCoords = latLngToCoords;

@johnd0e
Copy link
Collaborator

johnd0e commented Apr 17, 2020

I understand the issue, but I do not like proposed solution, as it is too specific.

Currently most of internals routines are not overridable. Even those that look like overridable.

E.g., the most straight-forward way to solve your issue could be overriding just single function: L.Util.formatNum, we just need to remove line 259 (which is excessive anyway), look here:

export function latLngToCoords(latlng, precision) {
precision = typeof precision === 'number' ? precision : 6;
return latlng.alt !== undefined ?
[Util.formatNum(latlng.lng, precision), Util.formatNum(latlng.lat, precision), Util.formatNum(latlng.alt, precision)] :

Unfortunately, if you look into built leaflet-src.js, you see that that wouldn't work:

function latLngToCoords(latlng, precision) {
	precision = typeof precision === 'number' ? precision : 6;
	return latlng.alt !== undefined ?
		[formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :

Thus allowing overriding of some arbitrary function looks like inconsistent decision.

@johnd0e
Copy link
Collaborator

johnd0e commented Apr 17, 2020

I would better think about alternative approaches:

  1. Refactor to allow overriding of most internal routines
  2. Or solve your issue in some explicit way, e.g. what if we change function in this way:
// @function latLngToCoords(latlng: LatLng, precision?: Number or Function): Array
// precision:
// - if given value is `number` than apply `L.Util.formatNum` function to latlng values
// - if it is `function` than apply it to to format latlng values
function latLngToCoords(latlng, precision) {
  1. Alternatively: make overridable copy of formatNum in L.GeoJSON namespace.
  2. ???

What do think?

@johnd0e
Copy link
Collaborator

johnd0e commented Apr 18, 2020

I have an idea: enhance L.Util.formatNum to allow special false value of digits:

	if (digits === false) { return num; } // no formatting

@inkstak
Copy link
Contributor Author

inkstak commented Apr 21, 2020

Hi @johnd0e, thank you for looking into this old problem.

I like your idea, and the possibility to skip precision with toGeoJSON(false)
With this solution, this PR is no longer needed

@inkstak
Copy link
Contributor Author

inkstak commented May 11, 2020

Closed in favor of #7100

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants