-
Notifications
You must be signed in to change notification settings - Fork 944
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
New module @turf/transform-scale #759
Conversation
Keeping it simple is always the winner in my book! 👍 Looks good so far |
Tap will be used at the root level and not in the individual repos
* @name scale | ||
* @param {GeoJSON} geojson object to be scaled | ||
* @param {number} factor of scaling, positive values greater than 0 | ||
* @param {boolean} [fromCenter=true] point from which the scaling will occur; it will default to the most |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Love it!
We might need to re-word the JSDocs, sounds like the "default" is saying False
but in fact it's True
.
point from which the scaling will occur;
it will default
to the most south-west point of abbox
containing the geojson, if true the scaling will origin from thecentroid
of the geojson
if (!geojson) throw new Error('geojson is required'); | ||
|
||
if (geojson.type === 'Point') | ||
return (mutate === false || mutate === undefined) ? geojson : JSON.parse(JSON.stringify(geojson)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?? Why the multiple mutate === undefined
This should be on it's own and only defined once.
if (fromCenter === true || fromCenter === undefined) { | ||
origin = centroid(geojson); | ||
} else { | ||
var box = bbox(geojson); // [ minX, minY, maxX, maxY ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calculating BBox
can be slightly computational heavy, so if a bbox
is already provided in the geojson (which is defined in the spec) we can use that vs. re-calculating it.
http://geojson.org/geojson-spec.html#bounding-boxes
var box = (geojson.bbox) ? geojson.bbox : bbox(geojson);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good to know!
if (mutate === false || mutate === undefined) geojson = JSON.parse(JSON.stringify(geojson)); | ||
|
||
// Scale each coordinate | ||
coordEach(geojson, function (pointCoords) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Going to be using coord
as variable name since the singular term implies it's a point:
coord === [x, y]
coords === [[x1, y1], [x2, y2]]
@stebogit Got an idea for defining the point of origin. What if allow a param called var west = bbox[0];
var south = bbox[1];
var east = bbox[2];
var north = bbox[3];
This would allow an application to scale any GeoJSON in any 5 direction with relative ease. |
@DenisCarriere super! 👍 |
@stebogit Cool! I'll add that, shouldn't be too long. |
|
||
# scale | ||
|
||
Moves any geojson Feature or Geometry of a specified distance along a Rhumb Line |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@DenisCarriere probably you can find a better description for the module, explaining the Rhumb "motion" of the point; this however is the translate
description
if (!factor) throw new Error('factor is required'); | ||
|
||
// Default params | ||
var isMutate = mutate === false || mutate === undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this logic correct? If mutate
is false then isMutate
is true? Also, if mutate
is not defined then isMutate
is true. The logic below says if isMutate
is true then you clone the data. I think either the logic or the variable names are backwards. You pick.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say just:
if (mutate === true) geojson = JSON.parse(JSON.stringify(geojson));
dropping isMutate
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So if mutate
is true then you clone the data? Seems backwards.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Arrrgh! You're right! Dumb me...
Then
if (!mutate === true) geojson = JSON.parse(JSON.stringify(geojson));
Still no need for isMutate
Right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That should work. Though there should be enough tests to ensure that it works and the data is not cloning when it should be and vice versa.
However, I usually prefer logical conditions to be explicit and not implicit which require the reader to sit there and figure out what is going on and have to remember javascript falsey rules. Just my preference. But if it works it works.
|
||
// Shortcut no-scaling | ||
if (factor === 1) | ||
return (mutate === false || mutate === undefined) ? geojson : JSON.parse(JSON.stringify(geojson)); | ||
if (factor === 1) return geojson; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@DenisCarriere what about:
if (factor === 1 || geojson.geometry.type === 'Point' || geojson.type === 'Point') return geojson;
Scaling a Point
is pointless 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this would skip only Point
, not MultiPoint
. Wouldn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep MultiPoint won't be skipped. I'll add that in my next commit, wait out
@stebogit Scaling z by a multiplication factor is fine. Rhumb lines are just along the earth's surface. |
@stebogit You might consider allowing the |
* | ||
* @name scale | ||
* @param {GeoJSON} geojson object to be scaled | ||
* @param {number} factor of scaling, positive values greater than 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stebogit Couldn't it also be negative scaling?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@DenisCarriere how do you scale negative?
0 - 1
=> shrink
1 - Infinity
=> enlarge
< 0
?
0
=> a Point
? Does this make sense?
Why not |
@dpmcmlxxvi @DenisCarriere could the origin be anywhere? I personally find it weird, not obvious (it looks more like a |
I think lots of scaling operations would be performed from origin points that are not related to the features. For example, fixing sensor mis-calibrations, mis-registrations, etc. |
var box = (geojson.bbox) ? geojson.bbox : bbox(geojson); // [ west, south, east, north ] | ||
origin = point([box[0], box[1]]); | ||
} | ||
if (factor === 1 || isPoint) return geojson; | ||
|
||
// Clone geojson to avoid side effects | ||
if (isMutate) geojson = JSON.parse(JSON.stringify(geojson)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@DenisCarriere I'd move this before any return
, since the mutate
behaviour should be respected even if there is no actual scaling
. That's why my original multiple mutate === undefined
* @param {string} [fromCorner] sw/se/nw/ne/center | ||
* @returns {Feature<Point>} Point origin | ||
*/ | ||
function defineOrigin(geojson, fromCorner) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job! 👍
added tests with origin as point;
@DenisCarriere @dpmcmlxxvi I fixed a couple of errors in the calculation and added some more tests. Now I believe the output is correct. |
@stebogit @dpmcmlxxvi
Edit: Nevermind, that would most likely be the case for a FeatureCollection and not a MultiFeature. |
Handling FeatureCollection@stebogit FeatureCollection should be handled as an exception, if
Proposed (Multiple centroids per feature)Before (Single centroid origin) |
@stebogit Yep agreed, |
@stebogit It looks good to me. @DenisCarriere As for scaling each feature separately in a FeatureCollection, it's an interesting approach but I'm wondering what you expect the use case to be? What geospatial application would someone need to scale each feature separately from, say, their own centroid? I would think scaling each separately would be the exception and by default treat the collection as the others. |
@dpmcmlxxvi If you provide a FeatureCollection using the bare minimum parameters |
@dpmcmlxxvi @stebogit Good news!! No issues up in the Northern Latitudes (~75-80 lat) Bad (expected) news, some issues with a Fiji MultiPolygon.
|
To me the difference between |
@stebogit Agreed, FeatureCollection is simply a container of Features which |
The FeatureCollection handling logic is pretty straightforward. // Scale each Feature separately
if (geojson.type === 'FeatureCollection') {
featureEach(geojson, function (feature, index) {
geojson.features[index] = scale(feature, factor, origin);
});
return geojson;
}
// Scale Feature/Geometry
return scale(geojson, factor, origin); |
@DenisCarriere I guess when I think of a scaling operation I normally expect there to be one fixed origin from which to scale the entire dataset. However, I'm fine with the current implementation, as long as the documentation is clear on what it is doing, because if the user wants a scaling from a single origin they can just provide a |
@DenisCarriere @dpmcmlxxvi I guess we'd need this then (note // Scale each Feature separately
if (geojson.type === 'FeatureCollection' && !originIsPoint) {
featureEach(geojson, function (feature, index) {
geojson.features[index] = scale(feature, factor, origin);
});
return geojson;
}
// Scale Feature/Geometry
return scale(geojson, factor, origin); However this behaviour would need to be clearly documented, as I don't think is obvious |
@stebogit Actually, I think the current implementation handles that case as-is, it is just scaling the coordinates of each feature separately, as opposed to the coordinates of the entire collection, but either way it should be the same effect since the |
Agreed, would need to clarify the FeatureCollection behavior. @stebogit Added |
@DenisCarriere the 180th Meridian problem is fascinating, I didn't know about it! |
👍 Yes the 180th Meridian problem is a fun one! Don't sweat it too much since it's a common problem for A LOT of GIS applications to handle. Especially once you start handing a Geometry which spans on both sides of the 180th Meridian (50km buffer around Fiji is a good example of that). |
@DenisCarriere @dpmcmlxxvi I guess once we merge #770 and re-test everything we're good to merge this. Anything else missing? |
Should be good to go! We can update the fixtures once both PRs are in the master branch. |
Ref: #747
@DenisCarriere @dpmcmlxxvi
The output is currently not correct: it is scaled successfully, but for some reason it does not reflect the
factor
value.I implemented my proposal for the
origin
; I look forward to knowing your opinion.Also, the scaling along the z-coordinate is not along a Rhumb line, but a simple multiplication by 'factor'. Is there a way, if it make sense at all, to perform such a scaling on
z
?For the moment I'd keep it simple and consider only uniform scalings, i.e. scaling factor equal in all directions.