diff --git a/vtm-extras/src/org/oscim/tiling/source/geojson/GeoJsonTileDecoder.java b/vtm-extras/src/org/oscim/tiling/source/geojson/GeoJsonTileDecoder.java
new file mode 100644
index 00000000..9d7d0e6f
--- /dev/null
+++ b/vtm-extras/src/org/oscim/tiling/source/geojson/GeoJsonTileDecoder.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright 2014 Hannes Janetzek
+ *
+ * This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
+ *
+ * This program is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along with
+ * this program. If not, see .
+ */
+package org.oscim.tiling.source.geojson;
+
+import static com.fasterxml.jackson.core.JsonToken.END_ARRAY;
+import static com.fasterxml.jackson.core.JsonToken.END_OBJECT;
+import static com.fasterxml.jackson.core.JsonToken.FIELD_NAME;
+import static com.fasterxml.jackson.core.JsonToken.START_ARRAY;
+import static com.fasterxml.jackson.core.JsonToken.START_OBJECT;
+import static com.fasterxml.jackson.core.JsonToken.VALUE_NUMBER_FLOAT;
+import static com.fasterxml.jackson.core.JsonToken.VALUE_NUMBER_INT;
+import static com.fasterxml.jackson.core.JsonToken.VALUE_STRING;
+import static org.oscim.core.MercatorProjection.latitudeToY;
+import static org.oscim.core.MercatorProjection.longitudeToX;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.LinkedHashMap;
+import java.util.zip.GZIPInputStream;
+
+import org.oscim.core.GeometryBuffer.GeometryType;
+import org.oscim.core.MapElement;
+import org.oscim.core.Tile;
+import org.oscim.tiling.ITileDataSink;
+import org.oscim.tiling.source.ITileDecoder;
+import org.oscim.utils.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+
+public class GeoJsonTileDecoder implements ITileDecoder {
+ static final Logger log = LoggerFactory.getLogger(GeoJsonTileDecoder.class);
+
+ private final MapElement mMapElement;
+ private final GeoJsonTileSource mTileSource;
+ private final LinkedHashMap mTagMap;
+ private final JsonFactory mJsonFactory;
+
+ private final static char[] FIELD_FEATURES = "features".toCharArray();
+ private final static char[] FIELD_GEOMETRY = "geometry".toCharArray();
+ private final static char[] FIELD_PROPERTIES = "properties".toCharArray();
+ private final static char[] FIELD_COORDINATES = "coordinates".toCharArray();
+ private final static char[] FIELD_TYPE = "type".toCharArray();
+
+ private final static char[] LINETRING = "LineString".toCharArray();
+ private final static char[] POLYGON = "Polygon".toCharArray();
+ private final static char[] POINT = "Point".toCharArray();
+ private final static char[] MULTI_LINESTRING = "MultiLineString".toCharArray();
+ private final static char[] MULTI_POLYGON = "MultiPolygon".toCharArray();
+ private final static char[] MULTI_POINT = "MultiPoint".toCharArray();
+
+ private ITileDataSink mTileDataSink;
+
+ private double mTileY, mTileX, mTileScale;
+
+ GeoJsonTileDecoder(GeoJsonTileSource tileSource) {
+ mTileSource = tileSource;
+ mTagMap = new LinkedHashMap();
+ mJsonFactory = new JsonFactory();
+
+ mMapElement = new MapElement();
+ mMapElement.layer = 5;
+ }
+
+ @Override
+ public boolean decode(Tile tile, ITileDataSink sink, InputStream is) throws IOException {
+ mTileDataSink = sink;
+ mTileScale = 1 << tile.zoomLevel;
+ mTileX = tile.tileX / mTileScale;
+ mTileY = tile.tileY / mTileScale;
+ mTileScale *= Tile.SIZE;
+
+ is = new GZIPInputStream(is);
+
+ JsonParser jp = mJsonFactory.createParser(new InputStreamReader(is));
+
+ for (JsonToken t; (t = jp.nextToken()) != null;) {
+
+ if (t == FIELD_NAME) {
+
+ if (match(jp, FIELD_FEATURES)) {
+ if (jp.nextToken() != START_ARRAY)
+ continue;
+
+ while ((t = jp.nextToken()) != null) {
+ if (t == START_OBJECT)
+ parseFeature(jp);
+
+ if (t == END_ARRAY)
+ break;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ private void parseFeature(JsonParser jp)
+ throws JsonParseException, IOException {
+
+ mMapElement.clear();
+ mMapElement.tags.clear();
+ mTagMap.clear();
+
+ for (JsonToken t; (t = jp.nextToken()) != null;) {
+
+ if (t == FIELD_NAME) {
+
+ if (match(jp, FIELD_GEOMETRY)) {
+ if (jp.nextToken() == START_OBJECT)
+ parseGeometry(jp);
+ }
+
+ if (match(jp, FIELD_PROPERTIES)) {
+ if (jp.nextToken() == START_OBJECT)
+ parseProperties(jp);
+ }
+ continue;
+ }
+ if (t == END_OBJECT)
+ break;
+ }
+
+ //add tag information
+ mTileSource.decodeTags(mMapElement, mTagMap);
+ if (mMapElement.tags.numTags == 0)
+ return;
+
+ mTileSource.postGeomHook(mMapElement);
+
+ if (mMapElement.type == GeometryType.NONE)
+ return;
+
+ //process this element
+ mTileDataSink.process(mMapElement);
+ }
+
+ private void parseProperties(JsonParser jp)
+ throws JsonParseException, IOException {
+ for (JsonToken t; (t = jp.nextToken()) != null;) {
+ if (t == FIELD_NAME) {
+ String text = jp.getCurrentName();
+
+ t = jp.nextToken();
+ if (t == VALUE_STRING) {
+ mTagMap.put(text, jp.getText());
+ } else if (t == VALUE_NUMBER_INT) {
+ mTagMap.put(text, jp.getNumberValue());
+ }
+ continue;
+ }
+ if (t == END_OBJECT)
+ break;
+ }
+ }
+
+ private void parseGeometry(JsonParser jp)
+ throws JsonParseException, IOException {
+
+ boolean multi = false;
+ GeometryType type = GeometryType.NONE;
+
+ for (JsonToken t; (t = jp.nextToken()) != null;) {
+ if (t == FIELD_NAME) {
+ if (match(jp, FIELD_COORDINATES)) {
+ if (jp.nextToken() != START_ARRAY)
+ continue;
+ if (multi) {
+ parseMulti(jp, type);
+ } else {
+ if (type == GeometryType.POLY)
+ parsePolygon(jp);
+
+ if (type == GeometryType.LINE)
+ parseLineString(jp);
+
+ if (type == GeometryType.POINT)
+ parseCoordinate(jp);
+
+ }
+ } else if (match(jp, FIELD_TYPE)) {
+ multi = false;
+
+ jp.nextToken();
+
+ if (match(jp, LINETRING))
+ type = GeometryType.LINE;
+ else if (match(jp, POLYGON))
+ type = GeometryType.POLY;
+ else if (match(jp, POINT))
+ type = GeometryType.POINT;
+ else if (match(jp, MULTI_LINESTRING)) {
+ type = GeometryType.LINE;
+ multi = true;
+ }
+ else if (match(jp, MULTI_POLYGON)) {
+ type = GeometryType.POLY;
+ multi = true;
+ }
+ else if (match(jp, MULTI_POINT)) {
+ type = GeometryType.POINT;
+ multi = true;
+ }
+
+ if (type == GeometryType.POINT)
+ mMapElement.startPoints();
+ }
+ continue;
+ }
+ if (t == END_OBJECT)
+ break;
+ }
+ }
+
+ private void parseMulti(JsonParser jp, GeometryType type)
+ throws JsonParseException, IOException {
+
+ for (JsonToken t; (t = jp.nextToken()) != null;) {
+ if (t == END_ARRAY)
+ break;
+
+ if (t == START_ARRAY) {
+ if (type == GeometryType.POLY)
+ parsePolygon(jp);
+
+ else if (type == GeometryType.LINE)
+ parseLineString(jp);
+
+ else if (type == GeometryType.POINT)
+ parseCoordinate(jp);;
+
+ } else {
+ //....
+ }
+ }
+ }
+
+ private void parsePolygon(JsonParser jp)
+ throws JsonParseException, IOException {
+ int ring = 0;
+
+ for (JsonToken t; (t = jp.nextToken()) != null;) {
+ if (t == START_ARRAY) {
+ if (ring == 0)
+ mMapElement.startPolygon();
+ else
+ mMapElement.startHole();
+
+ ring++;
+ parseCoordSequence(jp);
+ removeLastPoint();
+ continue;
+ }
+
+ if (t == END_ARRAY)
+ break;
+ }
+ }
+
+ private void removeLastPoint() {
+ mMapElement.pointPos -= 2;
+ mMapElement.index[mMapElement.indexPos] -= 2;
+ }
+
+ private void parseLineString(JsonParser jp)
+ throws JsonParseException, IOException {
+ mMapElement.startLine();
+ parseCoordSequence(jp);
+ }
+
+ private void parseCoordSequence(JsonParser jp)
+ throws JsonParseException, IOException {
+
+ for (JsonToken t; (t = jp.nextToken()) != null;) {
+
+ if (t == START_ARRAY) {
+ parseCoordinate(jp);
+ continue;
+ }
+
+ if (t == END_ARRAY)
+ break;
+
+ }
+ }
+
+ private void parseCoordinate(JsonParser jp)
+ throws JsonParseException, IOException {
+ int pos = 0;
+ double x = 0, y = 0; //, z = 0;
+
+ for (JsonToken t; (t = jp.nextToken()) != null;) {
+ if (t == VALUE_NUMBER_FLOAT || t == VALUE_NUMBER_INT) {
+
+ // avoid String allocation (by getDouble...)
+ char[] val = jp.getTextCharacters();
+ int offset = jp.getTextOffset();
+ int length = jp.getTextLength();
+ double c = ArrayUtils.parseNumber(val, offset, offset + length);
+
+ if (pos == 0)
+ x = c;
+ if (pos == 1)
+ y = c;
+ //if (pos == 2)
+ //z = c;
+
+ pos++;
+ continue;
+ }
+
+ if (t == END_ARRAY)
+ break;
+ }
+
+ mMapElement.addPoint((float) ((longitudeToX(x) - mTileX) * mTileScale),
+ (float) ((latitudeToY(y) - mTileY) * mTileScale));
+
+ }
+
+ private final static boolean match(JsonParser jp, char[] fieldName)
+ throws JsonParseException, IOException {
+
+ int length = jp.getTextLength();
+ if (length != fieldName.length)
+ return false;
+
+ char[] val = jp.getTextCharacters();
+ int offset = jp.getTextOffset();
+
+ for (int i = 0; i < length; i++) {
+ if (fieldName[i] != val[i + offset])
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/vtm-extras/src/org/oscim/tiling/source/geojson/GeoJsonTileSource.java b/vtm-extras/src/org/oscim/tiling/source/geojson/GeoJsonTileSource.java
new file mode 100644
index 00000000..e76d63a8
--- /dev/null
+++ b/vtm-extras/src/org/oscim/tiling/source/geojson/GeoJsonTileSource.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2014 Hannes Janetzek
+ *
+ * This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
+ *
+ * This program is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along with
+ * this program. If not, see .
+ */
+package org.oscim.tiling.source.geojson;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.oscim.core.MapElement;
+import org.oscim.core.Tag;
+import org.oscim.tiling.ITileDataSource;
+import org.oscim.tiling.source.LwHttp;
+import org.oscim.tiling.source.UrlTileDataSource;
+import org.oscim.tiling.source.UrlTileSource;
+
+public abstract class GeoJsonTileSource extends UrlTileSource {
+
+ public GeoJsonTileSource(String url) {
+ super(url);
+ setExtension(".json");
+ }
+
+ @Override
+ public ITileDataSource getDataSource() {
+ Map opt = new HashMap();
+ opt.put("Accept-Encoding", "gzip");
+ return new UrlTileDataSource(this, new GeoJsonTileDecoder(this), new LwHttp(getUrl(), opt));
+ }
+
+ public Tag getFeatureTag() {
+ return null;
+ }
+
+ /** allow overriding tag handling */
+ public abstract void decodeTags(MapElement mapElement, Map properties);
+
+ public Tag rewriteTag(String key, Object value) {
+
+ if (value == null)
+ return null;
+
+ String val = (value instanceof String) ? (String) value : String.valueOf(value);
+
+ return new Tag(key, val);
+ }
+
+ /** modify mapElement before process() */
+ public void postGeomHook(MapElement mapElement) {
+
+ }
+}
diff --git a/vtm/src/org/oscim/utils/ArrayUtils.java b/vtm/src/org/oscim/utils/ArrayUtils.java
index 73815698..112be334 100644
--- a/vtm/src/org/oscim/utils/ArrayUtils.java
+++ b/vtm/src/org/oscim/utils/ArrayUtils.java
@@ -61,4 +61,81 @@ public class ArrayUtils {
right--;
}
}
+
+ public static double parseNumber(char[] str, int pos, int end) {
+
+ boolean neg = false;
+ if (str[pos] == '-') {
+ neg = true;
+ pos++;
+ }
+
+ double val = 0;
+ int pre = 0;
+ char c = 0;
+
+ for (; pos < end; pos++, pre++) {
+ c = str[pos];
+ if (c < '0' || c > '9') {
+ if (pre == 0)
+ throw new NumberFormatException("s " + c);
+
+ break;
+ }
+ val = val * 10 + (int) (c - '0');
+ }
+
+ if (pre == 0)
+ throw new NumberFormatException();
+
+ if (c == '.') {
+ float div = 10;
+ for (pos++; pos < end; pos++) {
+ c = str[pos];
+ if (c < '0' || c > '9')
+ break;
+ val = val + ((int) (c - '0')) / div;
+ div *= 10;
+ }
+ }
+
+ if (c == 'e' || c == 'E') {
+ // advance 'e'
+ pos++;
+
+ // check direction
+ int dir = 1;
+ if (str[pos] == '-') {
+ dir = -1;
+ pos++;
+ }
+ // skip leading zeros
+ for (; pos < end; pos++)
+ if (str[pos] != '0')
+ break;
+
+ int shift = 0;
+ for (pre = 0; pos < end; pos++, pre++) {
+ c = str[pos];
+ if (c < '0' || c > '9') {
+ // nothing after 'e'
+ if (pre == 0)
+ throw new NumberFormatException("e " + c);
+ break;
+ }
+ shift = shift * 10 + (int) (c - '0');
+ }
+
+ // guess it's ok for sane values of E
+ if (dir > 0) {
+ while (shift-- > 0)
+ val *= 10;
+ } else {
+ while (shift-- > 0)
+ val /= 10;
+ }
+ }
+
+ return neg ? -val : val;
+ }
}