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

Handle Elasticsearch GeoJSON circles using interpolation #67

Merged
merged 1 commit into from
Apr 23, 2018

Conversation

warwickdufour
Copy link

@warwickdufour warwickdufour commented Apr 4, 2018

Proposed code to be able to render Elasticsearch GeoJSON circle geometry in GeoServer. The interpolation angular interval is varied according to the circumference of the circle (in ellipsoid surface units as opposed to pixels) and kept between upper and lower bounds.

An example output of NOTAMs over Cyprus is shown below. Note that the circles are displayed as 'sort-of' ellipses due to the latitude.
screenshot from 2018-04-04 14-16-36

Copy link
Contributor

@sjudeng sjudeng left a comment

Choose a reason for hiding this comment

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

Thanks for the contribution. For consistency could you update to use four spaces for indent on all new lines?

Copy link
Contributor

@sjudeng sjudeng left a comment

Choose a reason for hiding this comment

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

Looks close. Some mostly minor feedback below. Thanks again for the contribution.

static {
GEO_HASH_PATTERN = Pattern.compile("[0123456789bcdefghjkmnpqrstuvwxyz]+");
}

private static final Pattern ELASTIC_DISTANCE_PATTERN = Pattern.compile("([0-9]+(\\.[0-9]+)?)([a-zA-Z]+)");

private final GeodeticCalculator geodeticCalculator = new GeodeticCalculator(DefaultEllipsoid.WGS84);
Copy link
Contributor

Choose a reason for hiding this comment

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

Move geodeticCalculator initialization to constructor

Copy link
Author

Choose a reason for hiding this comment

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

Done

case "CIRCLE": {
final List posList;
posList = (List) properties.get("coordinates");
String radius = (String) properties.get("radius");
Copy link
Contributor

Choose a reason for hiding this comment

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

final

Copy link
Author

Choose a reason for hiding this comment

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

Assume 'final' means that this is OK?

*/
private Geometry createCircle(Coordinate centreCoord, String radius) {

String[] distanceAndUnitStrings = getDistanceAndUnitStrings(radius);
Copy link
Contributor

Choose a reason for hiding this comment

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

final here and below

Copy link
Author

Choose a reason for hiding this comment

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

Ditto

private Geometry createCircle(Coordinate centreCoord, String radius) {

String[] distanceAndUnitStrings = getDistanceAndUnitStrings(radius);
double radM = FilterToElasticHelper.convertToMeters(Double.valueOf(distanceAndUnitStrings[0]), distanceAndUnitStrings[1]);
Copy link
Contributor

Choose a reason for hiding this comment

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

Split into two lines to keep line length under 120

Copy link
Author

Choose a reason for hiding this comment

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

Renamed variable to keep on one line

// Interpolate a circle on the surface of the ellipsoid such that the
// distance between interpolated point does not exceed (somewhat
// arbitrarily) 500m with the caveats that a circle must have a minimum
// of 40 points and a maximum of 500.
Copy link
Contributor

Choose a reason for hiding this comment

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

Ideally do you think the 500m and [40,500] values would be store parameters? If so would you create a separate issue to track that? Don't have to make the update here.

Copy link
Author

Choose a reason for hiding this comment

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

Agree that they should be 'store parameters' (not too sure what they are but I'm sure all will become clear if I put the effort in). Not sure if I should create a separate issue when the code I'm submitting has not been merged into the master branch?

*/
private Geometry createCircle(Coordinate centreCoord, String radius) {

String[] distanceAndUnitStrings = getDistanceAndUnitStrings(radius);
Copy link
Contributor

Choose a reason for hiding this comment

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

If radius is invalid ({"",""} result or exception) geometry should be null?

Copy link
Author

Choose a reason for hiding this comment

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

Agree, done.

assertEquals("mm", distanceAndUnitStrings[1]);
distanceAndUnitStrings = ElasticParserUtil.getDistanceAndUnitStrings("999xyz");
assertEquals("999", distanceAndUnitStrings[0]);
assertEquals("xyz", distanceAndUnitStrings[1]);
Copy link
Contributor

Choose a reason for hiding this comment

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

Add test for no unit. Also add test for invalid value/unit.

Copy link
Author

Choose a reason for hiding this comment

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

Done

public static double convertToMeters(double distance, String unit) {
Double conversion = UNITS_MAP.get(unit);
if (conversion == null) {
conversion = new Double(1.0);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think return of null would be better for invalid unit and it keeps it in line with previous behavior in toMeters.

Copy link
Author

Choose a reason for hiding this comment

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

On reflection I think I should revert to original to Meters() method and usage (and remove introduced methods) so functionality remains as you expect and I will create a private method in ElasticParserUtil for use when handling circles. Only change would be to make extended UNITS_MAP package private for reuse in ElasticParserUtil. Please ignore latest push, another soon to follow.

delegate.queryBuilder = ImmutableMap.of("bool", ImmutableMap.of("must_not", delegate.queryBuilder));
}
}

public static double convertToMeters(double distance, String unit) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Add javadoc for public method

Copy link
Author

Choose a reason for hiding this comment

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

n/a as method being removed

static {
GEO_HASH_PATTERN = Pattern.compile("[0123456789bcdefghjkmnpqrstuvwxyz]+");
}

private static final Pattern ELASTIC_DISTANCE_PATTERN = Pattern.compile("([0-9]+(\\.[0-9]+)?)([a-zA-Z]+)");
Copy link
Contributor

Choose a reason for hiding this comment

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

Docs suggest units are optional but pattern looks like it's required. Can you add a test for the missing/default unit case?

Copy link
Author

Choose a reason for hiding this comment

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

Done

Copy link
Contributor

@sjudeng sjudeng left a comment

Choose a reason for hiding this comment

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

Thanks for the updates and additional tests. A few more minor comments below. The project follows the Sun Coding Conventions for consistency with GeoServer (see here) and the code style update requests below are based on those conventions.

private static final int MIN_CIRCLE_POINTS = 40;
private static final double MIN_CIRCLE_RADIUS_M = 0.001;


Copy link
Contributor

Choose a reason for hiding this comment

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

Remove extra line

Copy link
Author

Choose a reason for hiding this comment

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

Done

case "CIRCLE": {
final List posList;
posList = (List) properties.get("coordinates");
String radius = (String) properties.get("radius");
Copy link
Contributor

Choose a reason for hiding this comment

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

Add final modifier (e.g. final String radius = ...).

Copy link
Author

Choose a reason for hiding this comment

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

Done

return null;
}

double radM;
Copy link
Contributor

Choose a reason for hiding this comment

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

Use final modifier (final double radM). Same comment on all variable declarations in this code block.

Copy link
Author

Choose a reason for hiding this comment

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

Done

}

// Reject circles with radii below an arbitrary minimum.
if(radM < MIN_CIRCLE_RADIUS_M) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Add space (e.g. if (radM...) for consistency with style in file/project. Same comment on other if statements below.

Copy link
Author

Choose a reason for hiding this comment

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

Done

throw new IllegalArgumentException();
}
Matcher matcher = ELASTIC_DISTANCE_PATTERN.matcher(distanceWithUnit);
double distance;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can declare and initialize distance on one line below (e.g. final double distance = Double...). Same comment for unit.

Copy link
Author

Choose a reason for hiding this comment

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

Done

if (unit != null && ! unit.isEmpty()) {
throw new IllegalArgumentException("Illegal unit: " + unit);
}
else {
Copy link
Contributor

Choose a reason for hiding this comment

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

One line } else {

Copy link
Author

Choose a reason for hiding this comment

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

Done

}
return distance * conversion;
} else {
throw new IllegalArgumentException();
Copy link
Contributor

Choose a reason for hiding this comment

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

Add message


private static final long serialVersionUID = 1L;

{
// Metric
Copy link
Contributor

Choose a reason for hiding this comment

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

Fix indentation on comment (here and for // Other below)

Copy link
Author

Choose a reason for hiding this comment

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

Done

public Geometry createGeometry(final Map<String, Object> properties) {
final Geometry geometry;
switch (String.valueOf(properties.get("type")).toUpperCase()) {
case "POINT": {
Copy link
Contributor

Choose a reason for hiding this comment

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

Keep case statement aligned with switch

Copy link
Contributor

@sjudeng sjudeng left a comment

Choose a reason for hiding this comment

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

Looks great! Thanks for your patience on the review. Could you squash your commits for merge?

…interpolation with a great circle line radius.
@warwickdufour
Copy link
Author

I think the squashing has worked OK but as it's the first time I have done it and there was an issue when pushing to 'origin' (had to redo and put a '+' in front of the branch name to get it to work) you might like to check.

@sjudeng sjudeng merged commit cf2d672 into ngageoint:master Apr 23, 2018
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.

2 participants