S3DB layer (#475)

This commit is contained in:
Gustl22 2018-01-07 22:18:41 +01:00 committed by Emux
parent 85342ddd84
commit ca9786dca3
No known key found for this signature in database
GPG Key ID: 89C6921D7AF2BDD0
6 changed files with 1622 additions and 25 deletions

View File

@ -41,7 +41,7 @@ import java.util.Set;
public class BuildingLayer extends Layer implements TileLoaderThemeHook {
private final static int BUILDING_LEVEL_HEIGHT = 280; // cm
protected final static int BUILDING_LEVEL_HEIGHT = 280; // cm
private final static int MIN_ZOOM = 17;
private final static int MAX_ZOOM = 17;
@ -52,7 +52,7 @@ public class BuildingLayer extends Layer implements TileLoaderThemeHook {
private static final Object BUILDING_DATA = BuildingLayer.class.getName();
// Can be replaced with Multimap in Java 8
private HashMap<Integer, List<BuildingElement>> mBuildings = new HashMap<>();
protected HashMap<Integer, List<BuildingElement>> mBuildings = new HashMap<>();
class BuildingElement {
MapElement element;
@ -93,6 +93,8 @@ public class BuildingLayer extends Layer implements TileLoaderThemeHook {
@Override
public boolean process(MapTile tile, RenderBuckets buckets, MapElement element,
RenderStyle style, int level) {
// FIXME check why some buildings are processed up to 4 times (should avoid overhead)
// FIXME fix artifacts at tile borders
if (!(style instanceof ExtrusionStyle))
return false;
@ -128,7 +130,7 @@ public class BuildingLayer extends Layer implements TileLoaderThemeHook {
* @param extrusion the style of map element
* @param tile the tile which contains map element
*/
private void processElement(MapElement element, ExtrusionStyle extrusion, MapTile tile) {
protected void processElement(MapElement element, ExtrusionStyle extrusion, MapTile tile) {
int height = 0; // cm
int minHeight = 0; // cm
@ -162,7 +164,7 @@ public class BuildingLayer extends Layer implements TileLoaderThemeHook {
*
* @param tile the tile which contains stored map elements
*/
private void processElements(MapTile tile) {
protected void processElements(MapTile tile) {
if (!mBuildings.containsKey(tile.hashCode()))
return;

View File

@ -0,0 +1,283 @@
/*
* Copyright 2018 Gustl22
*
* 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.layers.tile.buildings;
import org.oscim.core.Box;
import org.oscim.core.GeometryBuffer;
import org.oscim.core.MapElement;
import org.oscim.core.Tag;
import org.oscim.core.TagSet;
import org.oscim.core.Tile;
import org.oscim.layers.tile.MapTile;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.oscim.renderer.MapRenderer.COORD_SCALE;
/**
* An experimental layer to display S3DB elements.
*/
public class S3DBLayer extends BuildingLayer {
private static final Logger log = LoggerFactory.getLogger(S3DBLayer.class);
private final float TILE_SCALE = (ExtrusionUtils.REF_TILE_SIZE / (Tile.SIZE * COORD_SCALE));
private boolean mColored = true;
public S3DBLayer(Map map, VectorTileLayer tileLayer) {
super(map, tileLayer, true);
}
public boolean isColored() {
return mColored;
}
public void setColored(boolean colored) {
mColored = colored;
}
@Override
public void complete(MapTile tile, boolean success) {
super.complete(tile, success);
// Do stuff here
}
@Override
public void processElement(MapElement element, ExtrusionStyle extrusion, MapTile tile) {
float groundScale = tile.getGroundScale();
int maxHeight = 0; // cm
int minHeight = 0; // cm
int roofHeight = 0;
// Get roof height
String v = element.tags.getValue(Tag.KEY_ROOF_HEIGHT);
if (v != null) {
roofHeight = (int) (Float.parseFloat(v) * 100);
} else if ((v = element.tags.getValue(Tag.KEY_ROOF_LEVELS)) != null) {
roofHeight = (int) (Float.parseFloat(v) * BUILDING_LEVEL_HEIGHT);
} else if ((v = element.tags.getValue(Tag.KEY_ROOF_ANGLE)) != null) {
Box bb = null;
for (int k = 0; k < element.index[0]; k += 2) {
float p1 = element.points[k];
float p2 = element.points[k + 1];
if (bb == null)
bb = new Box(p1, p2, p1, p2);
else {
bb.add(p1, p2);
}
}
if (bb != null) {
float maxSize = (int) Math.max(bb.getHeight(), bb.getWidth()) * TILE_SCALE;
roofHeight = (int) ((Float.parseFloat(v) / 45.f) * (maxSize * 15)); // Angle is simplified, 15 is some constant, may depend on lat
}
} else if ((v = element.tags.getValue(Tag.KEY_ROOF_SHAPE)) != null && !v.equals(Tag.VALUE_FLAT)) {
roofHeight = (2 * BUILDING_LEVEL_HEIGHT);
}
// Get building height
v = element.tags.getValue(Tag.KEY_HEIGHT);
if (v != null) {
maxHeight = (int) (Float.parseFloat(v) * 100);
} else {
// #TagFromTheme: generalize level/height tags
if ((v = element.tags.getValue(Tag.KEY_BUILDING_LEVELS)) != null) {
maxHeight = (int) (Float.parseFloat(v) * BUILDING_LEVEL_HEIGHT);
maxHeight += roofHeight;
}
}
if (maxHeight == 0)
maxHeight = extrusion.defaultHeight * 100;
v = element.tags.getValue(Tag.KEY_MIN_HEIGHT);
if (v != null)
minHeight = (int) (Float.parseFloat(v) * 100);
else {
// #TagFromTheme: level/height tags
if ((v = element.tags.getValue(Tag.KEY_BUILDING_MIN_LEVEL)) != null)
minHeight = (int) (Float.parseFloat(v) * BUILDING_LEVEL_HEIGHT);
}
// Get building color
Integer bColor = null;
if (mColored) {
if (element.tags.containsKey(Tag.KEY_BUILDING_COLOR)) {
bColor = S3DBUtils.getColor(element.tags.getValue(Tag.KEY_BUILDING_COLOR), false);
} else if (element.tags.containsKey(Tag.KEY_BUILDING_MATERIAL)) {
bColor = S3DBUtils.getMaterialColor(element.tags.getValue(Tag.KEY_BUILDING_MATERIAL), false);
}
}
if (bColor == null) {
bColor = extrusion.colorTop;
}
// Scale x, y and z axis
ExtrusionUtils.mapPolyCoordScale(element);
float minHeightS = ExtrusionUtils.mapGroundScale(minHeight, groundScale) * TILE_SCALE;
float maxHeightS = ExtrusionUtils.mapGroundScale(maxHeight, groundScale) * TILE_SCALE;
float minRoofHeightS = ExtrusionUtils.mapGroundScale(maxHeight - roofHeight, groundScale) * TILE_SCALE;
// Process building and roof
processRoof(element, tile, minRoofHeightS, maxHeightS, bColor);
if (S3DBUtils.calcOutlines(element, minHeightS, minRoofHeightS)) {
get(tile).addMeshElement(element, groundScale, bColor);
}
}
@Override
protected void processElements(MapTile tile) {
if (!mBuildings.containsKey(tile.hashCode()))
return;
List<BuildingElement> tileBuildings = mBuildings.get(tile.hashCode());
Set<BuildingElement> rootBuildings = new HashSet<>();
for (BuildingElement partBuilding : tileBuildings) {
if (!partBuilding.isPart)
continue;
TagSet partTags = partBuilding.element.tags;
String refId = partTags.getValue(Tag.KEY_REF); // #TagFromTheme
refId = refId == null ? partTags.getValue("root_id") : refId; // Mapzen
if (refId == null)
continue;
// Search buildings which inherit parts
for (BuildingElement rootBuilding : tileBuildings) {
if (rootBuilding.isPart
|| !(refId.equals(rootBuilding.element.tags.getValue(Tag.KEY_ID))))
continue;
if (mColored) {
TagSet rootTags = rootBuilding.element.tags;
for (int i = 0; i < rootTags.size(); i++) {
Tag rTag = rootTags.get(i);
if ((rTag.key.equals(Tag.KEY_COLOR) && !partTags.containsKey(Tag.KEY_MATERIAL)
|| rTag.key.equals(Tag.KEY_ROOF_COLOR) && !partTags.containsKey(Tag.KEY_ROOF_MATERIAL)
|| rTag.key.equals(Tag.KEY_ROOF_SHAPE))
&& !partTags.containsKey(rTag.key)) {
partTags.add(rTag);
}
}
}
rootBuildings.add(rootBuilding);
break;
}
}
tileBuildings.removeAll(rootBuildings); // root buildings aren't rendered
for (BuildingElement buildingElement : tileBuildings) {
processElement(buildingElement.element, buildingElement.style, tile);
}
mBuildings.remove(tile.hashCode());
}
/**
* Process the roof parts of building.
*
* @param element the MapElement which needs a roof
* @param tile the tile which contains map element
* @param minHeight the height of the underlying building
* @param maxHeight the height of the roof + minHeight (whole building)
* @param buildingColor the color of main building
*/
private void processRoof(MapElement element, MapTile tile, float minHeight, float maxHeight, int buildingColor) {
Integer roofColor = null;
String v;
if (mColored) {
v = element.tags.getValue(Tag.KEY_ROOF_COLOR);
if (v != null)
roofColor = S3DBUtils.getColor(v, true);
else if ((v = element.tags.getValue(Tag.KEY_ROOF_MATERIAL)) != null)
roofColor = S3DBUtils.getMaterialColor(v, true);
}
boolean roofOrientationAcross = false;
if ((v = element.tags.getValue(Tag.KEY_ROOF_ORIENTATION)) != null) {
if (v.equals(Tag.VALUE_ACROSS)) {
roofOrientationAcross = true;
}
}
// Calc roof shape
v = element.tags.getValue(Tag.KEY_ROOF_SHAPE);
if (v == null) {
v = Tag.VALUE_FLAT;
}
float groundScale = tile.getGroundScale();
GeometryBuffer gElement = new GeometryBuffer(element);
GeometryBuffer specialParts = null;
if (roofColor == null) roofColor = buildingColor;
boolean success = false;
switch (v) {
case Tag.VALUE_DOME:
case Tag.VALUE_ONION:
success = S3DBUtils.calcCircleMesh(gElement, minHeight, maxHeight, v);
break;
case Tag.VALUE_ROUND:
case Tag.VALUE_SALTBOX:
case Tag.VALUE_GABLED:
case Tag.VALUE_GAMBREL:
specialParts = new GeometryBuffer(0, 0); // No data in GeometryBuffer needed
success = S3DBUtils.calcRidgeMesh(gElement, minHeight, maxHeight, roofOrientationAcross, true, specialParts);
break;
case Tag.VALUE_MANSARD:
case Tag.VALUE_HALF_HIPPED:
case Tag.VALUE_HIPPED:
success = S3DBUtils.calcRidgeMesh(gElement, minHeight, maxHeight, roofOrientationAcross, false, null);
break;
case Tag.VALUE_SKILLION:
// ROOF_SLOPE_DIRECTION is not supported yet
String roofDirection = element.tags.getValue(Tag.KEY_ROOF_DIRECTION);
float roofDegree = 0;
if (roofDirection != null) {
roofDegree = Float.parseFloat(roofDirection);
}
specialParts = new GeometryBuffer(element);
success = S3DBUtils.calcSkillionMesh(gElement, minHeight, maxHeight, roofDegree, specialParts);
break;
case Tag.VALUE_PYRAMIDAL:
success = S3DBUtils.calcPyramidalMesh(gElement, minHeight, maxHeight);
break;
case Tag.VALUE_FLAT:
default:
success = S3DBUtils.calcFlatMesh(gElement, minHeight);
break;
}
if (success) {
get(tile).addMeshElement(gElement, groundScale, roofColor);
if (specialParts != null) {
get(tile).addMeshElement(specialParts, groundScale, buildingColor);
}
} else {
log.debug("Roof calculation failed: " + element.toString());
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -20,6 +20,7 @@ package org.oscim.renderer.bucket;
import org.oscim.backend.canvas.Color;
import org.oscim.core.GeometryBuffer;
import org.oscim.core.Tile;
import org.oscim.utils.ExtrusionUtils;
import org.oscim.utils.FastMath;
import org.oscim.utils.KeyMap;
import org.oscim.utils.KeyMap.HashItem;
@ -361,13 +362,9 @@ public class ExtrusionBucket extends RenderBucket {
float[] points = element.points;
/* 10 cm steps */
float sfactor = 1 / 10f;
height *= sfactor;
minHeight *= sfactor;
/* match height with ground resultion (meter per pixel) */
height /= mGroundResolution;
minHeight /= mGroundResolution;
/* match height with ground resolution (meter per pixel) */
height = ExtrusionUtils.mapGroundScale(height, mGroundResolution);
minHeight = ExtrusionUtils.mapGroundScale(minHeight, mGroundResolution);
boolean complexOutline = false;
boolean simpleOutline = true;

View File

@ -0,0 +1,51 @@
/*
* Copyright 2012, 2013 Hannes Janetzek
* Copyright 2018 Gustl22
*
* 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.utils;
import org.oscim.core.GeometryBuffer;
import org.oscim.core.Tile;
/**
* A utility class with extrusion helper methods.
*/
public final class ExtrusionUtils {
public static final float REF_TILE_SIZE = 4096.0f; // Standard ref tile size
/**
* @param value the height value
* @param groundScale the ground scale of tile
* @return the corresponding height in the specific tile
*/
public static float mapGroundScale(float value, float groundScale) {
/* match height with ground resolution (meter per pixel) */
return value / (groundScale * 10); // 10 cm steps
}
/**
* Map the raw buffer scale to scale of coordinates
*/
public static void mapPolyCoordScale(GeometryBuffer buffer) {
float tileScale = REF_TILE_SIZE / Tile.SIZE;
float[] points = buffer.points;
for (int pPos = 0; pPos < buffer.pointNextPos; pPos++) {
points[pPos] = points[pPos] * tileScale;
}
}
private ExtrusionUtils() {
}
}

View File

@ -20,6 +20,7 @@ package org.oscim.utils.geom;
import java.util.ArrayList;
import java.util.List;
// TODO Utils can be improved e.g. by avoiding object creations
public final class GeometryUtils {
private GeometryUtils() {
@ -101,19 +102,19 @@ public final class GeometryUtils {
if (bisection[0] == 0 && bisection[1] == 0) {
// 90 degree to v1
bisection[0] = v1[1];
bisection[1] = v1[0];
bisection[1] = -v1[0];
}
return bisection;
}
/**
* @param a first vector
* @param b second vector (same length as a)
* @param b second vector
* @return a - b
*/
public static float[] diffVec(float[] a, float[] b) {
float[] diff = new float[a.length];
for (int i = 0; i < a.length; i++) {
float[] diff = new float[Math.min(a.length, b.length)];
for (int i = 0; i < diff.length; i++) {
diff[i] = a[i] - b[i];
}
return diff;
@ -121,12 +122,12 @@ public final class GeometryUtils {
/**
* @param a first vector
* @param b second vector (same length as a)
* @param b second vector
* @return a + b
*/
public static float[] sumVec(float[] a, float[] b) {
float[] add = new float[a.length];
for (int i = 0; i < a.length; i++) {
float[] add = new float[Math.min(a.length, b.length)];
for (int i = 0; i < add.length; i++) {
add[i] = b[i] + a[i];
}
return add;
@ -182,6 +183,18 @@ public final class GeometryUtils {
return Math.sqrt(dx * dx + dy * dy);
}
/**
* @param pP point
* @param pL point of line
* @param vL vector of line
* @return the minimum distance between line and point
*/
public static float distancePointLine2D(float[] pP, float[] pL, float[] vL) {
float[] vPL = diffVec(pL, pP);
float[] vPS = diffVec(vPL, scale(vL, dotProduct(vPL, vL)));
return (float) Math.sqrt(dotProduct(vPS, vPS));
}
public static double dotProduct(float[] p, int a, int b, int c) {
double ux = (p[b] - p[a]);
@ -260,14 +273,11 @@ public final class GeometryUtils {
}
/**
* @return more than 0 if points of triangle are clockwise, 0 if triangle is a line,
* else less than 0.
* @return a positive value, if pA-pB-pC makes a counter-clockwise turn,
* negative for clockwise turn, and zero if the points are collinear.
*/
public static float isTrisClockwise(float[] pA, float[] pB, float[] pC) {
float v = (pB[0] - pA[0]) * (pB[1] + pA[1]);
v += (pC[0] - pB[0]) * (pC[1] + pB[1]);
v += (pA[0] - pC[0]) * (pA[1] + pC[1]);
return v;
return (pB[0] - pA[0]) * (pC[1] - pA[1]) - (pB[1] - pA[1]) * (pC[0] - pA[0]);
}
/**