Overpass tile source (#665)

This commit is contained in:
Gustl22 2019-02-19 13:39:22 +01:00 committed by Emux
parent c0c8abe3a7
commit 23d65486e6
No known key found for this signature in database
GPG Key ID: 64ED9980896038C3
7 changed files with 454 additions and 19 deletions

View File

@ -0,0 +1,228 @@
/*
* Copyright 2014 Hannes Janetzek
* Copyright 2017 devemux86
* Copyright 2019 Gustl22
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.oscim.tiling.source.overpass;
import org.oscim.core.GeometryBuffer.GeometryType;
import org.oscim.core.MapElement;
import org.oscim.core.Tag;
import org.oscim.core.TagSet;
import org.oscim.core.Tile;
import org.oscim.core.osm.OsmData;
import org.oscim.core.osm.OsmElement;
import org.oscim.core.osm.OsmNode;
import org.oscim.core.osm.OsmRelation;
import org.oscim.core.osm.OsmWay;
import org.oscim.tiling.ITileDataSink;
import org.oscim.tiling.source.ITileDecoder;
import org.oscim.tiling.source.mapfile.OSMUtils;
import org.oscim.utils.overpass.OverpassAPIReader;
import java.io.IOException;
import java.io.InputStream;
import static org.oscim.core.MercatorProjection.latitudeToY;
import static org.oscim.core.MercatorProjection.longitudeToX;
public class OverpassTileDecoder implements ITileDecoder {
private final MapElement mMapElement;
private ITileDataSink mTileDataSink;
private double mTileY, mTileX, mTileScale;
public OverpassTileDecoder() {
mMapElement = new MapElement();
mMapElement.layer = 5;
}
public synchronized boolean decode(Tile tile, ITileDataSink sink, InputStream is) {
mTileDataSink = sink;
mTileScale = 1 << tile.zoomLevel;
mTileX = tile.tileX / mTileScale;
mTileY = tile.tileY / mTileScale;
mTileScale *= Tile.SIZE;
OsmData data;
try {
OverpassAPIReader reader = new OverpassAPIReader();
reader.parse(is);
data = reader.getData();
} catch (IOException e) {
e.printStackTrace();
return false;
}
for (OsmNode element : data.getNodes())
parseFeature(element);
for (OsmWay element : data.getWays())
parseFeature(element);
for (OsmRelation element : data.getRelations())
parseFeature(element);
return true;
}
private synchronized void parseFeature(OsmElement element) {
if (element.tags == null || element.tags.size() == 0)
return;
synchronized (mMapElement) {
mMapElement.clear();
mMapElement.tags.clear();
mMapElement.tags.set(element.tags);
//add tag information
decodeTags(mMapElement);
parseGeometry(element);
if (mMapElement.type == GeometryType.NONE)
return;
mTileDataSink.process(mMapElement);
}
}
private void parseGeometry(OsmElement element) {
//TODO mulipolygons
if (element instanceof OsmWay) {
boolean linearFeature = !OSMUtils.isArea(mMapElement);
if (linearFeature) {
mMapElement.type = GeometryType.LINE;
parseLine((OsmWay) element);
} else {
mMapElement.type = GeometryType.POLY;
parsePolygon((OsmWay) element);
}
} else if (element instanceof OsmNode) {
mMapElement.type = GeometryType.POINT;
mMapElement.startPoints();
parseCoordinate((OsmNode) element);
}
}
private void parsePolygon(OsmWay element) {
//int ring = 0;
//for (element.rings) {
//if (ring == 0)
mMapElement.startPolygon();
//else
// mMapElement.startHole();
//ring++;
parseCoordSequence(element);
removeLastPoint();
//}
}
private void removeLastPoint() {
mMapElement.pointNextPos -= 2;
mMapElement.index[mMapElement.indexCurrentPos] -= 2;
}
private void parseLine(OsmWay element) {
mMapElement.startLine();
parseCoordSequence(element);
}
private void parseCoordSequence(OsmWay element) {
for (OsmNode node : element.nodes)
parseCoordinate(node);
}
private void parseCoordinate(OsmNode element) {
mMapElement.addPoint((float) ((longitudeToX(element.lon) - mTileX) * mTileScale),
(float) ((latitudeToY(element.lat) - mTileY) * mTileScale));
}
private void decodeTags(MapElement mapElement) {
TagSet tags = mapElement.tags;
Tag tag = tags.get(Tag.KEY_ROOF_DIRECTION);
if (tag != null) {
if (!isNumeric(tag.value)) {
switch (tag.value.toLowerCase()) {
case "n":
case "north":
tag.value = "0";
break;
case "e":
case "east":
tag.value = "90";
break;
case "s":
case "south":
tag.value = "180";
break;
case "w":
case "west":
tag.value = "270";
break;
case "ne":
tag.value = "45";
break;
case "se":
tag.value = "135";
break;
case "sw":
tag.value = "225";
break;
case "nw":
tag.value = "315";
break;
case "nne":
tag.value = "22";
break;
case "ene":
tag.value = "67";
break;
case "ese":
tag.value = "112";
break;
case "sse":
tag.value = "157";
break;
case "ssw":
tag.value = "202";
break;
case "wsw":
tag.value = "247";
break;
case "wnw":
tag.value = "292";
break;
case "nnw":
tag.value = "337";
break;
}
}
}
}
private static boolean isNumeric(String str) {
try {
Float.parseFloat(str);
} catch (NumberFormatException nfe) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,83 @@
/*
* Copyright 2013 Hannes Janetzek
* Copyright 2018 devemux86
* Copyright 2019 Gustl22
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.oscim.tiling.source.overpass;
import org.oscim.core.BoundingBox;
import org.oscim.core.Tile;
import org.oscim.tiling.ITileDataSource;
import org.oscim.tiling.OverzoomTileDataSource;
import org.oscim.tiling.source.UrlTileDataSource;
import org.oscim.tiling.source.UrlTileSource;
import org.oscim.utils.overpass.OverpassAPIReader;
public class OverpassTileSource extends UrlTileSource {
private static final String DEFAULT_URL = "https://www.overpass-api.de/api/interpreter?data=[out:json];";
private static final String DEFAULT_PATH = "(node{{bbox}};way{{bbox}};>;);out%20body;";
public static class Builder<T extends Builder<T>> extends UrlTileSource.Builder<T> {
public Builder() {
super(DEFAULT_URL, DEFAULT_PATH);
zoomMax(17);
}
@Override
public OverpassTileSource build() {
return new OverpassTileSource(this);
}
}
@SuppressWarnings("rawtypes")
public static Builder<?> builder() {
return new Builder();
}
public OverpassTileSource(Builder<?> builder) {
super(builder);
setUrlFormatter(new TileUrlFormatter() {
@Override
public String formatTilePath(UrlTileSource tileSource, Tile tile) {
BoundingBox bb = tile.getBoundingBox();
String query = OverpassAPIReader.query(
bb.getMinLongitude(),
bb.getMaxLongitude(),
bb.getMaxLatitude(),
bb.getMinLatitude(),
DEFAULT_PATH);
/*String encoded;
try {
query = URLEncoder.encode(query, "utf-8");
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
return null;
}*/
return query;
}
});
}
@Override
public ITileDataSource getDataSource() {
return new OverzoomTileDataSource(new UrlTileDataSource(this, new OverpassTileDecoder(), getHttpEngine()), mOverZoom);
}
}

View File

@ -1,6 +1,7 @@
/*
* Copyright 2013 Hannes Janetzek
* Copyright 2016 devemux86
* Copyright 2019 Gustl22
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
*
@ -75,25 +76,36 @@ public class OverpassAPIReader {
private final String query;
public OverpassAPIReader() {
this.query = null;
}
/**
* Creates a new instance with the specified geographical coordinates.
*
* @param left The longitude marking the left edge of the bounding box.
* @param right The longitude marking the right edge of the bounding box.
* @param top The latitude marking the top edge of the bounding box.
* @param bottom The latitude marking the bottom edge of the bounding box.
* @param baseUrl (optional) The base url of the server (eg.
* http://www.openstreetmap.org/api/0.5).
*/
public OverpassAPIReader(final double left, final double right,
final double top, final double bottom, final String baseUrl,
final double top, final double bottom,
final String query) {
this.query = query(left, right, top, bottom, query);
}
/**
* @param left The longitude marking the left edge of the bounding box.
* @param right The longitude marking the right edge of the bounding box.
* @param top The latitude marking the top edge of the bounding box.
* @param bottom The latitude marking the bottom edge of the bounding box.
* @param query The prepared query.
* @return the processed query with specified bounding box
*/
public static String query(final double left, final double right,
final double top, final double bottom,
final String query) {
String bbox = "(" + Math.min(top, bottom) + "," + Math.min(left, right)
+ "," + Math.max(top, bottom) + "," + Math.max(left, right)
+ ")";
this.query = query.replaceAll("\\{\\{bbox\\}\\}", bbox);
return query.replaceAll("\\{\\{bbox\\}\\}", bbox);
}
@ -335,14 +347,13 @@ public class OverpassAPIReader {
System.out.println(msg);
}
public OsmData getData() {
public void parseInputStream() {
String encoded;
try {
encoded = URLEncoder.encode(this.query, "utf-8");
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
return null;
return;
}
System.out.println(myBaseUrl + "?data=" + encoded);
@ -363,6 +374,9 @@ public class OverpassAPIReader {
}
inputStream = null;
}
}
public OsmData getData() {
for (Entry<OsmRelation, List<TmpRelation>> entry : relationMembersForRelation
.entrySet()) {
@ -392,8 +406,8 @@ public class OverpassAPIReader {
}
}
}
log("nodes: " + ownNodes.size() + " ways: " + ownWays.size()
+ " relations: " + ownRelations.size());
/*log("nodes: " + ownNodes.size() + " ways: " + ownWays.size()
+ " relations: " + ownRelations.size());*/
// give up references to original collections
nodesById = null;

View File

@ -5,6 +5,7 @@ dependencies {
file("${rootDir}/vtm-desktop/natives").eachDir() { dir ->
implementation files(dir.path)
}
implementation project(':vtm-extras')
implementation project(':vtm-gdx-poi3d')
implementation project(':vtm-http')
implementation project(':vtm-jeo')

View File

@ -0,0 +1,88 @@
/*
* Copyright 2016-2018 devemux86
* Copyright 2019 Gustl22
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.oscim.test;
import org.oscim.core.MapPosition;
import org.oscim.gdx.GdxMapApp;
import org.oscim.layers.GroupLayer;
import org.oscim.layers.tile.bitmap.BitmapTileLayer;
import org.oscim.layers.tile.buildings.BuildingLayer;
import org.oscim.layers.tile.buildings.S3DBLayer;
import org.oscim.layers.tile.vector.VectorTileLayer;
import org.oscim.layers.tile.vector.labeling.LabelLayer;
import org.oscim.map.Map;
import org.oscim.map.Viewport;
import org.oscim.theme.VtmThemes;
import org.oscim.tiling.TileSource;
import org.oscim.tiling.source.OkHttpEngine;
import org.oscim.tiling.source.bitmap.DefaultSources;
import org.oscim.tiling.source.overpass.OverpassTileSource;
/**
* Use Overpass API data for vector layer.
* Only for developing as can be error-prone.
* Take care of overpass provider licenses.
*/
public class OverpassTest extends GdxMapApp {
@Override
public void createLayers() {
Map map = getMap();
TileSource tileSource = OverpassTileSource.builder()
.httpFactory(new OkHttpEngine.OkHttpFactory())
.zoomMin(15)
.zoomMax(17)
.build();
VectorTileLayer l = map.setBaseMap(tileSource);
TileSource bitmapTileSource = DefaultSources.OPENSTREETMAP
.httpFactory(new OkHttpEngine.OkHttpFactory())
.zoomMax(15)
.fadeSteps(new BitmapTileLayer.FadeStep[]{
new BitmapTileLayer.FadeStep(15, 16, 1f, 0f),
new BitmapTileLayer.FadeStep(16, Viewport.MAX_ZOOM_LEVEL, 0f, 0f)
})
.build();
mMap.layers().add(new BitmapTileLayer(mMap, bitmapTileSource));
BuildingLayer.RAW_DATA = true;
GroupLayer groupLayer = new GroupLayer(mMap);
groupLayer.layers.add(new S3DBLayer(map, l));
groupLayer.layers.add(new LabelLayer(map, l));
map.layers().add(groupLayer);
map.setTheme(VtmThemes.DEFAULT);
MapPosition pos = MapPreferences.getMapPosition();
if (pos != null)
map.setMapPosition(pos);
else
map.setMapPosition(53.075, 8.808, 1 << 17);
}
@Override
public void dispose() {
MapPreferences.saveMapPosition(mMap.getMapPosition());
super.dispose();
}
public static void main(String[] args) {
GdxMapApp.init();
GdxMapApp.run(new OverpassTest());
}
}

View File

@ -35,6 +35,7 @@ import org.oscim.renderer.bucket.RenderBuckets;
import org.oscim.theme.IRenderTheme;
import org.oscim.theme.styles.ExtrusionStyle;
import org.oscim.theme.styles.RenderStyle;
import org.oscim.utils.geom.GeometryUtils;
import java.util.ArrayList;
import java.util.HashMap;
@ -53,6 +54,11 @@ public class BuildingLayer extends Layer implements TileLoaderThemeHook, ZoomLim
*/
public static boolean POST_AA = false;
/**
* Use real time calculations to pre-process data.
*/
public static boolean RAW_DATA = false;
/**
* Let vanish extrusions / meshes which are covered by others.
* {@link org.oscim.renderer.bucket.RenderBucket#EXTRUSION}: roofs are always translucent.
@ -147,6 +153,10 @@ public class BuildingLayer extends Layer implements TileLoaderThemeHook, ZoomLim
mBuildings.put(tile.hashCode(), buildingElements);
}
element = new MapElement(element); // Deep copy, because element will be cleared
if (RAW_DATA && element.isClockwise() > 0) {
// Buildings must be counter clockwise in VTM (mirrored to OSM)
element.reverse();
}
buildingElements.add(new BuildingElement(element, extrusion));
return true;
}
@ -216,8 +226,13 @@ public class BuildingLayer extends Layer implements TileLoaderThemeHook, ZoomLim
// Search buildings which inherit parts
for (BuildingElement rootBuilding : tileBuildings) {
if (rootBuilding.element.isBuildingPart()
|| !(refId.equals(getValue(rootBuilding.element, Tag.KEY_ID))))
if (rootBuilding.element.isBuildingPart())
continue;
if (RAW_DATA) {
float[] center = GeometryUtils.center(partBuilding.element.points, 0, partBuilding.element.pointNextPos, null);
if (!GeometryUtils.pointInPoly(center[0], center[1], rootBuilding.element.points, rootBuilding.element.index[0], 0))
continue;
} else if (!(refId.equals(getValue(rootBuilding.element, Tag.KEY_ID))))
continue;
rootBuildings.add(rootBuilding);

View File

@ -27,6 +27,7 @@ import org.oscim.layers.tile.vector.VectorTileLayer;
import org.oscim.map.Map;
import org.oscim.theme.styles.ExtrusionStyle;
import org.oscim.utils.ExtrusionUtils;
import org.oscim.utils.geom.GeometryUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -191,15 +192,20 @@ public class S3DBLayer extends BuildingLayer {
continue;
String refId = getValue(partBuilding.element, Tag.KEY_REF);
if (refId == null)
if (!RAW_DATA && refId == null)
continue;
TagSet partTags = partBuilding.element.tags;
// Search buildings which inherit parts
for (BuildingElement rootBuilding : tileBuildings) {
if (rootBuilding.element.isBuildingPart()
|| !(refId.equals(rootBuilding.element.tags.getValue(Tag.KEY_ID))))
if (rootBuilding.element.isBuildingPart())
continue;
if (RAW_DATA) {
float[] center = GeometryUtils.center(partBuilding.element.points, 0, partBuilding.element.pointNextPos, null);
if (!GeometryUtils.pointInPoly(center[0], center[1], rootBuilding.element.points, rootBuilding.element.index[0], 0))
continue;
} else if (!refId.equals(rootBuilding.element.tags.getValue(Tag.KEY_ID)))
continue;
if ((getValue(rootBuilding.element, Tag.KEY_ROOF_SHAPE) != null)