/* * Copyright (c) 2015 Ushahidi Inc * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program in the file LICENSE-AGPL. If not, see * https://www.gnu.org/licenses/agpl-3.0.html */ package com.ushahidi.platform.mobile.app.presentation.util; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.PolygonOptions; import com.google.android.gms.maps.model.PolylineOptions; import com.cocoahero.android.geojson.Feature; import com.cocoahero.android.geojson.FeatureCollection; import com.cocoahero.android.geojson.GeoJSON; import com.cocoahero.android.geojson.Geometry; import com.cocoahero.android.geojson.GeometryCollection; import com.cocoahero.android.geojson.LineString; import com.cocoahero.android.geojson.MultiLineString; import com.cocoahero.android.geojson.MultiPoint; import com.cocoahero.android.geojson.MultiPolygon; import com.cocoahero.android.geojson.Point; import com.cocoahero.android.geojson.Polygon; import com.ushahidi.platform.mobile.app.BuildConfig; import com.ushahidi.platform.mobile.app.presentation.model.ClusterMarkerModel; import org.json.JSONArray; import org.json.JSONException; import android.graphics.Color; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.text.TextUtils; import java.util.ArrayList; import java.util.List; import timber.log.Timber; /** * Ported from https://goo.gl/wENBL7 to make it work with Android native maps * * @author Ushahidi Team <team@ushahidi.com> */ public final class GeoJsonLoadUtility { private GeoJsonLoadUtility() { // No instance } /** * Load GeoJSON from URL (in synchronous manner) and return GeoJSON FeatureCollection * * @param geojsonText of file in assets directory * @return the parsed getGeoJson as a featurecollection * @throws JSONException The json exception to be thrown */ public static FeatureCollection parseGeoJson( final String geojsonText) throws JSONException { if (TextUtils.isEmpty(geojsonText)) { throw new NullPointerException("Please provide a valid Geojson."); } if (BuildConfig.DEBUG) { Timber.d(GeoJsonLoadUtility.class.getCanonicalName(), "Loading GeoJSON URL: " + geojsonText); } FeatureCollection parsed = (FeatureCollection) GeoJSON.parse(geojsonText); if (BuildConfig.DEBUG) { Timber.d(GeoJsonLoadUtility.class.getCanonicalName(), "Parsed GeoJSON with " + parsed.getFeatures().size() + " features."); } return parsed; } /** * /** * Converts GeoJSON objects into UI Objects to be rendered on Google Maps * * @param featureCollection Parsed GeoJSON Objects * @param fillColor Optional fill color * @param strokeColor Optional stroke color * @return Collection of Mapbox SDK UI Objects * @throws JSONException The JSON exception to be thrown */ public static ArrayList<Object> createUIObjectsFromGeoJSONObjects( final FeatureCollection featureCollection, @ColorInt final int fillColor, @ColorInt final int strokeColor) throws JSONException { ArrayList<Object> uiObjects = new ArrayList<>(); for (Feature f : featureCollection.getFeatures()) { // Parse Into UI Objections long id = 0; if (f.getProperties().has("id")) { id = f.getProperties().getLong("id"); } final String title = f.getProperties().getString("title"); final String description = f.getProperties().getString("description"); if (f.getGeometry() instanceof GeometryCollection) { GeometryCollection geometryCollection = (GeometryCollection) f.getGeometry(); for (Geometry geometry : geometryCollection.getGeometries()) { deserializeGeometry(geometry, id, title, description, uiObjects, fillColor, strokeColor); } } else { deserializeGeometry(f.getGeometry(), id, title, description, uiObjects, fillColor, strokeColor); } } return uiObjects; } private static void deserializeGeometry(Geometry geometry, long id, String title, String description, ArrayList<Object> uiObjects, final int fillColor, final int strokeColor) throws JSONException { if (geometry instanceof Point) { setPoint(geometry, id, title, description, uiObjects); } else if (geometry instanceof MultiPoint) { setMultiPoint(geometry, id, title, description, uiObjects); } else if (geometry instanceof LineString) { setLineString(geometry, uiObjects, strokeColor); } else if (geometry instanceof MultiLineString) { setMultiLineString(geometry, uiObjects, strokeColor); } else if (geometry instanceof Polygon) { setPolygon(geometry, uiObjects, fillColor, strokeColor); } else if (geometry instanceof MultiPolygon) { setMultiPloygon(geometry, uiObjects, fillColor, strokeColor); } } private static void setPoint(Geometry geometry, long id, String title, String description, ArrayList<Object> uiObjects) throws JSONException { JSONArray coordinates = (JSONArray) geometry.toJSON().get("coordinates"); ClusterMarkerModel clusterMarkerModel = getClusterMarkerModel(coordinates, id, title, description); uiObjects.add(clusterMarkerModel); } private static void setMultiPoint(Geometry geometry, long id, String title, String description, ArrayList<Object> uiObjects) throws JSONException { int j; JSONArray points = (JSONArray) geometry.toJSON().get("coordinates"); for (j = 0; j < points.length(); j++) { JSONArray coordinates = (JSONArray) points.get(j); ClusterMarkerModel clusterMarkerModel = getClusterMarkerModel(coordinates, id, title, description); uiObjects.add(clusterMarkerModel); } } private static void setLineString(Geometry geometry, ArrayList<Object> uiObjects, int strokeColor) throws JSONException { int j; List<LatLng> latLngs = new ArrayList<>(); JSONArray points = (JSONArray) geometry.toJSON().get("coordinates"); JSONArray coordinates; for (j = 0; j < points.length(); j++) { coordinates = (JSONArray) points.get(j); double lon = coordinates.getDouble(0); double lat = coordinates.getDouble(1); latLngs.add(new LatLng(lat, lon)); } PolylineOptions polylineOptions = new PolylineOptions(); polylineOptions.addAll(latLngs); polylineOptions.width(5); if (strokeColor > 0) { polylineOptions.color(strokeColor); } else { polylineOptions.color(Color.RED); } uiObjects.add(polylineOptions); } private static void setMultiLineString(Geometry geometry, ArrayList<Object> uiObjects, int strokeColor) throws JSONException { int j; JSONArray lines = (JSONArray) geometry.toJSON().get("coordinates"); for (int k = 0; k < lines.length(); k++) { PolylineOptions polylineOptions = new PolylineOptions(); JSONArray points = (JSONArray) lines.get(k); JSONArray coordinates; for (j = 0; j < points.length(); j++) { coordinates = (JSONArray) points.get(j); double lon = coordinates.getDouble(0); double lat = coordinates.getDouble(1); polylineOptions.add(new LatLng(lat, lon)); } polylineOptions.width(5); if (strokeColor > 0) { polylineOptions.color(strokeColor); } else { polylineOptions.color(Color.RED); } uiObjects.add(polylineOptions); } } private static void setPolygon(Geometry geometry, ArrayList<Object> uiObjects, int fillColor, int strokeColor) throws JSONException { int j; PolygonOptions polygonOptions = new PolygonOptions(); JSONArray points = (JSONArray) geometry.toJSON().get("coordinates"); for (int r = 0; r < points.length(); r++) { JSONArray ring = (JSONArray) points.get(r); JSONArray coordinates; // we re-wind inner rings of GeoJSON polygons in order // to render them as transparent in the canvas layer. // first ring should have windingOrder = true, // all others should have winding order == false if ((r == 0 && !windingOrder(ring)) || (r != 0 && windingOrder(ring))) { for (j = 0; j < ring.length(); j++) { coordinates = (JSONArray) ring.get(j); double lon = coordinates.getDouble(0); double lat = coordinates.getDouble(1); polygonOptions.add(new LatLng(lat, lon)); } } else { for (j = ring.length() - 1; j >= 0; j--) { coordinates = (JSONArray) ring.get(j); double lon = coordinates.getDouble(0); double lat = coordinates.getDouble(1); polygonOptions.add(new LatLng(lat, lon)); } } if (strokeColor > 0) { polygonOptions.strokeColor(fillColor); } else { polygonOptions.strokeColor(Color.RED); } if (fillColor > 0) { polygonOptions.fillColor(fillColor); } else { polygonOptions.fillColor(Color.RED); } uiObjects.add(polygonOptions); } } private static void setMultiPloygon(Geometry geometry, ArrayList<Object> uiObjects, int fillColor, int strokeColor) throws JSONException { int j; PolygonOptions polygonOptions = new PolygonOptions(); JSONArray polygons = (JSONArray) geometry.toJSON().get("coordinates"); for (int p = 0; p < polygons.length(); p++) { JSONArray points = (JSONArray) polygons.get(p); for (int r = 0; r < points.length(); r++) { JSONArray ring = (JSONArray) points.get(r); JSONArray coordinates; // we re-wind inner rings of GeoJSON polygons in order // to render them as transparent in the canvas layer. // first ring should have windingOrder = true, // all others should have winding order == false if ((r == 0 && !windingOrder(ring)) || (r != 0 && windingOrder(ring))) { for (j = 0; j < ring.length(); j++) { coordinates = (JSONArray) ring.get(j); double lon = coordinates.getDouble(0); double lat = coordinates.getDouble(1); polygonOptions.add(new LatLng(lat, lon)); } } else { for (j = ring.length() - 1; j >= 0; j--) { coordinates = (JSONArray) ring.get(j); double lon = coordinates.getDouble(0); double lat = coordinates.getDouble(1); polygonOptions.add(new LatLng(lat, lon)); } } if (strokeColor > 0) { polygonOptions.strokeColor(fillColor); } else { polygonOptions.strokeColor(Color.RED); } if (fillColor > 0) { polygonOptions.fillColor(fillColor); } else { polygonOptions.fillColor(Color.RED); } uiObjects.add(polygonOptions); } } } @NonNull private static ClusterMarkerModel getClusterMarkerModel(JSONArray coordinates, long id, String title, String description) throws JSONException { double lon = coordinates.getDouble(0); double lat = coordinates.getDouble(1); ClusterMarkerModel clusterMarkerModel = new ClusterMarkerModel(); clusterMarkerModel._id = id; clusterMarkerModel.setLatitude(lat); clusterMarkerModel.setLongitude(lon); clusterMarkerModel.setTitle(title); clusterMarkerModel.setDescription(description); return clusterMarkerModel; } private static boolean windingOrder(JSONArray ring) throws JSONException { float area = 0; if (ring.length() > 2) { for (int i = 0; i < ring.length() - 1; i++) { JSONArray p1 = (JSONArray) ring.get(i); JSONArray p2 = (JSONArray) ring.get(i + 1); area += rad((Double) p2.get(0) - (Double) p1.get(0)) * (2 + Math.sin( rad((Double) p1.get(1))) + Math.sin(rad((Double) p2.get(1)))); } } return area > 0; } private static double rad(double val) { return val * Math.PI / 180f; } }