Skip to content

Commit

Permalink
Add geojson parsing support
Browse files Browse the repository at this point in the history
  • Loading branch information
lognaturel committed Jan 31, 2022
1 parent 2b4dd89 commit 5b34936
Show file tree
Hide file tree
Showing 8 changed files with 239 additions and 17 deletions.
10 changes: 7 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,16 @@ sourceCompatibility = '1.8'

dependencies {
// Be sure to update dependencies in pom.xml to match
implementation 'joda-time:joda-time:2.10.10'
implementation 'org.slf4j:slf4j-api:1.7.31'
implementation 'joda-time:joda-time:2.10.13'
implementation 'org.slf4j:slf4j-api:1.7.33'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.1'

// Upgrade to version higher than 1.4 when Collect minSDK >= 26
implementation 'org.apache.commons:commons-csv:1.4'

compileOnly 'net.sf.kxml:kxml2:2.3.0'
testImplementation 'ch.qos.logback:logback-classic:1.2.3'

testImplementation 'ch.qos.logback:logback-classic:1.2.10'
testImplementation 'junit:junit:4.13.2'
testImplementation 'net.sf.kxml:kxml2:2.3.0'
testImplementation 'org.hamcrest:hamcrest-all:1.3'
Expand Down
Binary file modified javarosa-android-api-level-checker.zip
Binary file not shown.
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@
<version>2.10.10</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,12 @@ public static ExternalDataInstance build(String instanceSrc, String instanceId)
return new ExternalDataInstance(root, instanceId, instanceSrc);
}

private static TreeElement parseExternalInstance(String instanceSrc, String instanceId) throws IOException, InvalidReferenceException, InvalidStructureException, XmlPullParserException, UnfullfilledRequirementsException {
private static TreeElement parseExternalInstance(String instanceSrc, String instanceId)
throws IOException, InvalidReferenceException, InvalidStructureException, XmlPullParserException, UnfullfilledRequirementsException {
String path = getPath(instanceSrc);
return instanceSrc.contains("file-csv") ?
CsvExternalInstance.parse(instanceId, path) : XmlExternalInstance.parse(instanceId, path);
return instanceSrc.contains("file-csv") ? CsvExternalInstance.parse(instanceId, path)
: instanceSrc.endsWith("geojson") ? GeoJsonExternalInstance.parse(instanceId, path)
: XmlExternalInstance.parse(instanceId, path);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package org.javarosa.core.model.instance;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.Map;
import org.javarosa.core.model.data.UncastData;
import org.javarosa.core.util.GeoUtils;

public class GeoJsonExternalInstance {
public static TreeElement parse(String instanceId, String path) throws IOException {
return parse(instanceId, new FileInputStream(path));
}

protected static TreeElement parse(String instanceId, InputStream geojsonStream) throws IOException {
final TreeElement root = new TreeElement("root", 0);
root.setInstanceName(instanceId);

ObjectMapper objectMapper = new ObjectMapper();
JsonNode geoJsonTree = objectMapper.readTree(geojsonStream);

if (!geoJsonTree.get("type").asText().equals("FeatureCollection")) {
throw new IOException("GeoJSON file must contain a top-level FeatureCollection");
}

JsonNode features = geoJsonTree.get("features");
int multiplicity = 0;

for (JsonNode feature : features) {
root.addChild(toTreeElement(feature, multiplicity));
multiplicity++;
}

return root;
}

private static TreeElement toTreeElement(JsonNode feature, int multiplicity) throws IOException {
TreeElement item = new TreeElement("item", multiplicity);

String geometry = GeoUtils.geojsonToOdkGeometry(feature.get("geometry"));
TreeElement geoField = new TreeElement("geometry", 0);
geoField.setValue(new UncastData(geometry));
item.addChild(geoField);

JsonNode properties = feature.get("properties");

for (Iterator<Map.Entry<String, JsonNode>> i = properties.fields(); i.hasNext();) {
Map.Entry<String, JsonNode> propertyEntry = i.next();
TreeElement field = new TreeElement(propertyEntry.getKey(), 0);
field.setValue(new UncastData(propertyEntry.getValue().asText()));
item.addChild(field);
}

return item;
}
}
25 changes: 18 additions & 7 deletions src/main/java/org/javarosa/core/util/GeoUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,19 @@

package org.javarosa.core.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;

import static java.lang.Math.PI;
import static java.lang.Math.abs;
import static java.lang.Math.acos;
import static java.lang.Math.cos;
import static java.lang.Math.sin;
import static java.lang.Math.toRadians;
import static java.lang.Math.PI;

import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Author: Meletis Margaritis
Expand Down Expand Up @@ -98,6 +99,16 @@ public static double calculateDistance(List<LatLong> points) {
return totalDistance;
}

public static String geojsonToOdkGeometry(JsonNode geojsonGeometry) throws IOException {
String type = geojsonGeometry.get("type").asText();
if (!type.equalsIgnoreCase("Point")) {
throw new IOException("Only Points are currently supported");
}

JsonNode coords = geojsonGeometry.withArray("coordinates");
return coords.get(0) + " " + coords.get(1) + " 0 0";
}

private static void logDistance(LatLong p1, LatLong p2, double distance, double totalDistance) {
logger.trace("\t{}\t{}\t{}\t{}\t{}\t{}",
p1.latitude, p1.longitude,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package org.javarosa.core.model.instance;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.fail;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.junit.Test;

public class GeoJsonExternalInstanceTest {
@Test
public void parse_throwsException_ifTopLevelElementNotFeatureCollection() {
try {
GeoJsonExternalInstance.parse("id", new ByteArrayInputStream(GEOMETRY_COLLECTION_GEOJSON.getBytes(StandardCharsets.UTF_8)));
fail("Exception expected");
} catch (Exception e) {
// expected
}
}

@Test
public void parse_parsesMultipleFeatures() throws IOException {
TreeElement featureCollection = GeoJsonExternalInstance.parse("id", new ByteArrayInputStream(FEATURE_COLLECTION_GEOJSON.getBytes(StandardCharsets.UTF_8)));
assertThat(featureCollection.getNumChildren(), is(2));
}

@Test
public void parse_addsGeometryAsChild() throws IOException {
TreeElement featureCollection = GeoJsonExternalInstance.parse("id", new ByteArrayInputStream(FEATURE_COLLECTION_GEOJSON.getBytes(StandardCharsets.UTF_8)));
assertThat(featureCollection.getChildAt(0).getChild("geometry", 0).getValue().getValue(), is("102 0.5 0 0"));
assertThat(featureCollection.getChildAt(1).getChild("geometry", 0).getValue().getValue(), is("104 0.5 0 0"));
}

@Test
public void parse_addsAllOtherPropertiesAsChildren() throws IOException {
TreeElement featureCollection = GeoJsonExternalInstance.parse("id", new ByteArrayInputStream(FEATURE_COLLECTION_GEOJSON.getBytes(StandardCharsets.UTF_8)));
assertThat(featureCollection.getChildAt(0).getNumChildren(), is(4));
assertThat(featureCollection.getChildAt(0).getChild("name", 0).getValue().getValue(), is("My cool point"));

assertThat(featureCollection.getChildAt(1).getNumChildren(), is(5));
assertThat(featureCollection.getChildAt(1).getChild("special-property", 0).getValue().getValue(), is("special value"));
}

private static String GEOMETRY_COLLECTION_GEOJSON = "{\n" +
" \"geometries\": [\n" +
" {\n" +
" \"coordinates\": [\n" +
" [\n" +
" 10.0,\n" +
" 11.2\n" +
" ],\n" +
" [\n" +
" 10.5,\n" +
" 11.9\n" +
" ]\n" +
" ],\n" +
" \"type\": \"Linestring\"\n" +
" },\n" +
" {\n" +
" \"coordinates\": [\n" +
" 10.0,\n" +
" 20.0\n" +
" ],\n" +
" \"type\": \"Point\"\n" +
" }\n" +
" ],\n" +
" \"type\": \"GeometryCollection\"\n" +
"}";

private static String FEATURE_COLLECTION_GEOJSON = "{\n" +
" \"type\": \"FeatureCollection\",\n" +
" \"features\": [\n" +
" {\n" +
" \"type\": \"Feature\",\n" +
" \"geometry\": {\n" +
" \"type\": \"Point\",\n" +
" \"coordinates\": [\n" +
" 102,\n" +
" 0.5\n" +
" ]\n" +
" },\n" +
" \"properties\": {\n" +
" \"id\": \"fs87b\",\n" +
" \"name\": \"My cool point\",\n" +
" \"foo\": \"bar\"\n" +
" }\n" +
" },\n" +
" {\n" +
" \"type\": \"Feature\",\n" +
" \"geometry\": {\n" +
" \"type\": \"Point\",\n" +
" \"coordinates\": [\n" +
" 104,\n" +
" 0.5\n" +
" ]\n" +
" },\n" +
" \"properties\": {\n" +
" \"id\": \"67abie\",\n" +
" \"name\": \"Your cool point\",\n" +
" \"foo\": \"quux\",\n" +
" \"special-property\": \"special value\"\n" +
" }\n" +
" }\n" +
" ]\n" +
"}";
}
41 changes: 37 additions & 4 deletions src/test/java/org/javarosa/core/util/GeoUtilsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,22 @@

package org.javarosa.core.util;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.javarosa.core.util.GeoUtils.EARTH_EQUATORIAL_CIRCUMFERENCE_METERS;
import static org.javarosa.core.util.GeoUtils.geojsonToOdkGeometry;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.javarosa.core.util.GeoUtils.EARTH_EQUATORIAL_CIRCUMFERENCE_METERS;
import static org.junit.Assert.assertEquals;

/**
* Tests features of GeoUtils. The tests whose names start with areaAndDistanceAreCorrectForPath
* use data from www.mapdevelopers.com/area_finder.php. They test that the enclosed area,
Expand Down Expand Up @@ -102,6 +108,33 @@ public void oneDegreeLongChgAt90Lat() {
}), 1e-6);
}

@Test
public void geojsonToOdkGeometry_convertsPointGeoJsonGeometry() throws IOException {
String point = "{ \"type\": \"Point\", \"coordinates\": [ 102, 0.5 ] }";
ObjectMapper objectMapper = new ObjectMapper();
JsonNode node = objectMapper.readTree(point);
assertThat(geojsonToOdkGeometry(node), is("102 0.5 0 0"));
}

@Test
public void geojsonToOdkGeometry_throwsException_ifGeometryNotPoint() throws IOException {
String notPoint = "{\n" +
" \"type\": \"LineString\",\n" +
" \"coordinates\": [\n" +
" [102.0, 0.0]\n" +
" ]\n" +
" }";
ObjectMapper objectMapper = new ObjectMapper();
JsonNode node = objectMapper.readTree(notPoint);

try {
geojsonToOdkGeometry(node);
fail("Exception expected");
} catch (IOException e) {
// expected
}
}

private double distance(double[][] points) {
return GeoUtils.calculateDistance(getLatLongs(points));
}
Expand Down

0 comments on commit 5b34936

Please sign in to comment.