add vtm-jeo

This commit is contained in:
Hannes Janetzek 2014-01-21 18:19:13 +01:00
parent 05b977be70
commit 9109da9784
6 changed files with 968 additions and 0 deletions

View File

@ -0,0 +1,80 @@
package org.oscim.layers;
import org.jeo.data.Dataset;
import org.jeo.map.Style;
import org.oscim.core.MapPosition;
import org.oscim.layers.JeoMapLoader.Task;
import org.oscim.map.Map;
import org.oscim.map.Map.UpdateListener;
import org.oscim.renderer.ElementRenderer;
import org.oscim.renderer.MapRenderer.Matrices;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class JeoMapLayer extends Layer implements UpdateListener {
public static final Logger log = LoggerFactory.getLogger(JeoMapLayer.class);
final org.jeo.map.View view;
private final org.jeo.map.Map mJeoMap;
private final JeoMapLoader mWorker;
public JeoMapLayer(Map map, Dataset data, Style style) {
super(map);
mJeoMap = org.jeo.map.Map.build().layer(data).style(style).map();
view = mJeoMap.getView();
mRenderer = new ElementRenderer() {
@Override
protected synchronized void update(MapPosition position, boolean changed,
Matrices matrices) {
if (mNewLayers != null) {
mMapPosition.copy(mNewLayers);
this.layers.clear();
this.layers.baseLayers = mNewLayers.layers;
mNewLayers = null;
compile();
log.debug("is ready " + isReady() + " " + layers.getSize());
}
}
};
mWorker = new JeoMapLoader(this);
mWorker.start();
}
@Override
public void onDetach() {
super.onDetach();
mWorker.awaitPausing();
try {
mWorker.join();
} catch (Exception e) {
log.error(e.toString());
}
}
@Override
public void onMapUpdate(MapPosition pos, boolean changed, boolean clear) {
if (changed) {
log.debug("go");
mWorker.go();
}
}
Task mNewLayers;
void setLayers(Task newLayers) {
synchronized (mRenderer) {
mNewLayers = newLayers;
}
mMap.render();
}
}

View File

@ -0,0 +1,346 @@
package org.oscim.layers;
// FIXME
// Apache License 2.0
import java.io.IOException;
import org.jeo.data.Dataset;
import org.jeo.data.Query;
import org.jeo.data.VectorDataset;
import org.jeo.feature.Feature;
import org.jeo.geom.CoordinatePath;
import org.jeo.geom.Envelopes;
import org.jeo.geom.Geom;
import org.jeo.map.CartoCSS;
import org.jeo.map.Map;
import org.jeo.map.RGB;
import org.jeo.map.Rule;
import org.jeo.map.RuleList;
import org.jeo.map.View;
import org.oscim.core.BoundingBox;
import org.oscim.core.GeometryBuffer;
import org.oscim.core.MapPosition;
import org.oscim.core.MercatorProjection;
import org.oscim.core.Tile;
import org.oscim.renderer.elements.ElementLayers;
import org.oscim.renderer.elements.LineLayer;
import org.oscim.renderer.elements.MeshLayer;
import org.oscim.renderer.elements.RenderElement;
import org.oscim.theme.renderinstruction.Line;
import org.oscim.utils.PausableThread;
import org.oscim.utils.TileClipper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
/**
* Does the work of actually rendering the map, outside of the ui thread.
*
* @author Justin Deoliveira, OpenGeo
* @author Hannes Janetzek, OpenScienceMap
*/
public class JeoMapLoader extends PausableThread {
static final Logger log = LoggerFactory.getLogger(JeoMapLoader.class);
private final JeoMapLayer mMapLayer;
public JeoMapLoader(JeoMapLayer mapLayer) {
mMapLayer = mapLayer;
}
private ElementLayers layers;
private final GeometryBuffer mGeom = new GeometryBuffer(128, 4);
private Task mCurrentTask;
private double mMinX;
private double mMinY;
@Override
protected void doWork() throws InterruptedException {
log.debug("start");
mWork = false;
Envelope env = new Envelope();
BoundingBox bbox = mMapLayer.mMap.getViewport().getViewBox();
env.init(bbox.getMinLongitude(), bbox.getMaxLongitude(),
bbox.getMinLatitude(), bbox.getMaxLatitude());
int w = mMapLayer.mMap.getWidth();
int h = mMapLayer.mMap.getHeight();
mMapLayer.view.setWidth(w);
mMapLayer.view.setHeight(h);
mClipper.setRect(-w, -h, w, h);
mMapLayer.view.zoomto(env);
Task task = new Task();
task.view = mMapLayer.view.clone();
mMapLayer.mMap.getMapPosition(task);
mCurrentTask = task;
layers = new ElementLayers();
Envelope b = task.view.getBounds();
// reduce lines points min distance
mMinX = ((b.getMaxX() - b.getMinX()) / task.view.getWidth()) * 2;
mMinY = ((b.getMaxY() - b.getMinY()) / task.view.getHeight()) * 2;
Map map = mMapLayer.view.getMap();
for (org.jeo.map.Layer l : map.getLayers()) {
if (!l.isVisible())
continue;
Dataset data = l.getData();
RuleList rules =
map.getStyle().getRules().selectById(l.getName(), true).flatten();
log.debug("data {}", data);
if (data instanceof VectorDataset) {
for (RuleList ruleList : rules.zgroup()) {
render(task.view, (VectorDataset) data, ruleList);
}
}
}
if (layers.baseLayers != null) {
mCurrentTask.layers = layers.baseLayers;
//layers.baseLayers = null;
//layers.clear();
mMapLayer.setLayers(mCurrentTask);
}
layers = null;
mCurrentTask = null;
}
void render(View view, VectorDataset data, RuleList rules) {
try {
Query q = new Query().bounds(view.getBounds());
log.debug("query {}", q);
// reproject
// if (data.getCRS() != null) {
// if (!Proj.equal(view.getCRS(), data.getCRS())) {
// q.reproject(view.getCRS());
// }
//}
//else {
// log.debug("Layer " + data.getName()
// + " specifies no projection, assuming map projection");
//}
for (Feature f : data.cursor(q)) {
RuleList rs = rules.match(f);
if (rs.isEmpty()) {
continue;
}
Rule r = rules.match(f).collapse();
if (r == null)
continue;
draw(view, f, r);
}
} catch (IOException e) {
log.error("Error querying layer " + data.getName() + e);
}
}
Geometry clipGeometry(View view, Geometry g) {
// TODO: doing a full intersection is sub-optimal,
// look at a more efficient clipping
// algorithm, like cohen-sutherland
return g.intersection(Envelopes.toPolygon(view.getBounds()));
}
void draw(View view, Feature f, Rule rule) {
Geometry g = f.geometry();
if (g == null) {
return;
}
// g = clipGeometry(view, g);
// if (g.isEmpty()) {
// return;
// }
switch (Geom.Type.from(g)) {
case POINT:
case MULTIPOINT:
//log.debug("draw point");
//drawPoint(f, rule);
return;
case LINESTRING:
case MULTILINESTRING:
//log.debug("draw line");
drawLine(f, rule, g);
return;
case POLYGON:
//Polygon p = (Polygon) g;
//p.reverse();
//log.debug("draw polygon");
drawPolygon(f, rule, g);
return;
case MULTIPOLYGON:
//log.debug("draw polygon");
for (int i = 0, n = g.getNumGeometries(); i < n; i++)
drawPolygon(f, rule, g.getGeometryN(i));
return;
default:
throw new UnsupportedOperationException();
}
}
private void drawLine(Feature f, Rule rule, Geometry g) {
LineLayer ll = layers.getLineLayer(0);
if (ll.line == null) {
RGB color = rule.color(f, CartoCSS.LINE_COLOR, RGB.black);
float width = rule.number(f, CartoCSS.LINE_WIDTH, 1.2f);
ll.line = new Line(0, color(color), width);
ll.width = width;
}
mGeom.clear();
mGeom.startLine();
CoordinatePath p = CoordinatePath.create(g);
path(mGeom, p);
//log.debug( ll.width + " add line " + mGeom.pointPos + " " + Arrays.toString(mGeom.points));
ll.addLine(mGeom);
}
TileClipper mClipper = new TileClipper(0, 0, 0, 0);
private void drawPolygon(Feature f, Rule rule, Geometry g) {
LineLayer ll = layers.getLineLayer(3);
if (ll.line == null) {
RGB color = rule.color(f, CartoCSS.POLYGON_FILL, RGB.red);
float width = rule.number(f, CartoCSS.LINE_WIDTH, 1.2f);
ll.line = new Line(2, color(color), width);
ll.width = width;
}
//PolygonLayer pl = layers.getPolygonLayer(1);
//
//if (pl.area == null) {
// RGB color = rule.color(f, CartoCSS.POLYGON_FILL, RGB.red);
// pl.area = new Area(1, color(color));
//}
MeshLayer mesh = layers.getMeshLayer(2);
mGeom.clear();
mGeom.startPolygon();
//mGeom.startLine();
CoordinatePath p = CoordinatePath.create(g).generalize(mMinX, mMinY);
if (path(mGeom, p) < 3)
return;
if (!mClipper.clip(mGeom))
return;
//log.debug(ll.width + " add poly " + mGeom.pointPos + " " + Arrays.toString(mGeom.points));
mesh.addMesh(mGeom);
ll.addLine(mGeom);
//pl.addPolygon(mGeom.points, mGeom.index);
}
public static int color(RGB rgb) {
return rgb.getAlpha() << 24
| rgb.getRed() << 16
| rgb.getGreen() << 8
| rgb.getBlue();
}
private int path(GeometryBuffer g, CoordinatePath path) {
MapPosition pos = mCurrentTask;
double scale = pos.scale * Tile.SIZE;
int cnt = 0;
O: while (path.hasNext()) {
Coordinate c = path.next();
float x = (float) ((MercatorProjection.longitudeToX(c.x) - pos.x) * scale);
float y = (float) ((MercatorProjection.latitudeToY(c.y) - pos.y) * scale);
switch (path.getStep()) {
case MOVE_TO:
if (g.isPoly())
g.startPolygon();
else if (g.isLine())
g.startLine();
cnt++;
g.addPoint(x, y);
break;
case LINE_TO:
cnt++;
g.addPoint(x, y);
break;
case CLOSE:
//g.addPoint(x, y);
//if (g.type == GeometryType.POLY)
break;
case STOP:
break O;
}
}
return cnt;
}
@Override
protected String getThreadName() {
return "JeoMapLayer";
}
@Override
protected boolean hasWork() {
return mWork;
}
boolean mWork;
public void go() {
if (hasWork())
return;
mWork = true;
synchronized (this) {
notifyAll();
}
}
static class Task extends MapPosition {
View view;
RenderElement layers;
}
}

View File

@ -0,0 +1,128 @@
package org.oscim.layers;
import java.io.File;
import java.io.IOException;
import org.jeo.carto.Carto;
import org.jeo.data.Dataset;
import org.jeo.data.Query;
import org.jeo.data.mem.MemVector;
import org.jeo.data.mem.MemWorkspace;
import org.jeo.feature.Feature;
import org.jeo.feature.Features;
import org.jeo.feature.Schema;
import org.jeo.feature.SchemaBuilder;
import org.jeo.geojson.GeoJSONDataset;
import org.jeo.geom.GeomBuilder;
import org.jeo.map.Style;
import com.vividsolutions.jts.geom.Geometry;
public class JeoTestData {
public static Style getStyle() {
Style style = null;
try {
style = Carto.parse("" +
"#things {" +
" line-color: #c80;" +
" polygon-fill: #00a;" +
"}" +
"#states {" +
" polygon-fill: #0dc;" +
"}"
);
return style;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static Dataset getJsonData(String file, boolean memory) {
GeoJSONDataset data = null;
try {
data = new GeoJSONDataset(new File(file));
} catch (UnsupportedOperationException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
if (memory) {
MemWorkspace mem = new MemWorkspace();
//mem.put("layer", data);
try {
Schema s = data.schema();
Query q = new Query();
MemVector memData = mem.create(s);
for (Feature f : data.cursor(q)) {
memData.add(f);
}
//return mem.get("layer");
return memData;
} catch (IOException e) {
e.printStackTrace();
}
}
return data;
}
public static Dataset getMemWorkspace(String layer) {
GeomBuilder gb = new GeomBuilder(4326);
MemWorkspace mem = new MemWorkspace();
Schema schema = new SchemaBuilder(layer)
.field("geometry", Geometry.class)
.field("id", Integer.class)
.field("name", String.class)
.field("cost", Double.class).schema();
MemVector data;
try {
data = mem.create(schema);
} catch (UnsupportedOperationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
Geometry g = gb.point(0, 0).toPoint();
//g.setSRID(4326);
data.add(Features.create(null, data.schema(),
g, 1, "anvil",
10.99));
data.add(Features.create(null, data.schema(),
gb.points(10, 10, 20, 20).toLineString(),
2, "bomb", 11.99));
data.add(Features.create(null, data.schema(),
gb.point(100, 10).toPoint().buffer(10),
3, "dynamite", 12.99));
//Dataset jsonData = new GeoJSONDataset(new File("states.json"));
//mem.put("states", jsonData);
try {
return mem.get(layer);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}

View File

@ -0,0 +1,74 @@
package org.oscim.theme.carto;
import static java.lang.System.out;
import java.util.List;
import java.util.Map;
import org.jeo.feature.BasicFeature;
import org.oscim.core.Tag;
import org.oscim.core.TagSet;
//imitate Feature behaviour for tags and zoom-level
class MatcherFeature extends BasicFeature {
TagSet mTags;
Integer mZoom;
void setTags(TagSet tags) {
mTags = tags;
}
void setZoom(int zoom) {
mZoom = Integer.valueOf(zoom);
}
protected MatcherFeature() {
super("");
}
@Override
public Object get(String key) {
//out.println("get(" + key + ")");
if (key.equals("zoom"))
return mZoom;
Tag t = mTags.get(key.intern());
if (t == null)
return null;
//out.println("value: " + t.value);
return t.value;
}
@Override
public void put(String key, Object val) {
out.println("EEEK put()");
}
@Override
public List<Object> list() {
out.println("EEEK list()");
return null;
}
@Override
public Map<String, Object> map() {
out.println("EEEK map()");
return null;
}
@Override
public Object get(int arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public void set(int arg0, Object arg1) {
// TODO Auto-generated method stub
}
};

View File

@ -0,0 +1,266 @@
package org.oscim.theme.carto;
import static java.lang.System.out;
import static org.jeo.map.CartoCSS.BACKGROUND_COLOR;
import static org.jeo.map.CartoCSS.OPACITY;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.jeo.carto.Carto;
import org.jeo.map.CartoCSS;
import org.jeo.map.RGB;
import org.jeo.map.Rule;
import org.jeo.map.RuleList;
import org.jeo.map.Selector;
import org.jeo.map.Style;
import org.oscim.core.GeometryBuffer.GeometryType;
import org.oscim.core.MapElement;
import org.oscim.core.Tag;
import org.oscim.core.TagSet;
import org.oscim.theme.IRenderTheme;
import org.oscim.theme.renderinstruction.Area;
import org.oscim.theme.renderinstruction.Line;
import org.oscim.theme.renderinstruction.RenderInstruction;
public class RenderTheme implements IRenderTheme {
final String STYLE = "" +
"[building = 'yes'] {" +
" z: 1;" +
" polygon-fill: #eee;" +
" [zoom >= 16] {" +
" polygon-fill: #c00;" +
" }" +
"}" +
"[admin_level = '2'] {" +
" line-color: #000;" +
" line-width: 1;" +
" z: 1;" +
"}" +
"[admin_level = '2'] {" +
" line-color: #000;" +
" line-width: 1;" +
" z: 1;" +
"}" +
"[admin_level = '4'] {" +
" line-color: #aaa;" +
" line-width: 1;" +
" z: 2;" +
"}" +
"[highway = 'motorway'] {" +
" line-color: #a00;" +
" z: 10;" +
"}" +
"[highway = 'primary'] {" +
" line-color: #aa0;" +
" z: 11;" +
"}" +
"[highway = 'residential'],[highway = 'road'],[highway = 'secondary'] {" +
" line-color: #fff;" +
" z: 12;" +
"}" +
" [landuse = 'forest'] {" +
" polygon-fill: #0a0;" +
" z: 2;" +
"}" +
"[natural = 'water'] {" +
" polygon-fill: #00a;" +
" z: 3;" +
"}";
private Style mStyle;
private RuleList mRules;
MatcherFeature mMatchFeature = new MatcherFeature();
private int mBackground;
public RenderTheme() {
try {
mStyle = loadStyle();
} catch (IOException e) {
e.printStackTrace();
}
// get map background
RuleList rules = mStyle.getRules().selectByName("Map", false);
if (!rules.isEmpty()) {
Rule rule = rules.collapse();
RGB bgColor = rule.color(null, BACKGROUND_COLOR, null);
if (bgColor != null) {
bgColor = bgColor.alpha(rule.number(null, OPACITY, 1f));
mBackground = color(bgColor);
}
}
mRules = mStyle.getRules();
//out.println(mRules);
//out.println();
if (mRules.get(1).equals(mRules.get(2)))
out.println("ok");
for (Rule r : mRules)
out.println(formatRule(r, 0));
}
class StyleSet {
int level;
RenderInstruction[] ri = new RenderInstruction[2];
}
Map<Rule, StyleSet> mStyleSets = new HashMap<Rule, StyleSet>();
int mCurLevel = 0;
public String formatRule(Rule r, int indent) {
StringBuilder sb = new StringBuilder();
String pad = "";
for (int i = 0; i < indent; i++)
pad += " ";
sb.append(pad);
for (Selector s : r.getSelectors()) {
sb.append(RuleDebug.formatSelector(s));
sb.append(",");
}
if (sb.length() > 0)
sb.setLength(sb.length() - 1);
sb.append(pad).append(" {").append("\n");
StyleSet s = new StyleSet();
RGB l = null;
RGB p = null;
if (r.properties().containsKey(CartoCSS.LINE_COLOR)) {
l = r.color(null, CartoCSS.LINE_COLOR, RGB.black);
}
if (r.properties().containsKey(CartoCSS.POLYGON_FILL)) {
p = r.color(null, CartoCSS.POLYGON_FILL, RGB.black);
}
if (p != null) {
s.ri[0] = new Area(mCurLevel++, color(p));
}
if (l != null) {
s.ri[1] = new Line(mCurLevel++, color(l), 1);
}
if (p != null || l != null) {
mStyleSets.put(r, s);
out.println("put " + s.ri[0] + s.ri[1]);
}
for (Map.Entry<String, Object> e : r.properties().entrySet()) {
sb.append(pad).append(" ").append(e.getKey()).append(": ").append(e.getValue())
.append(";\n");
}
for (Rule nested : r.nested()) {
sb.append(formatRule(nested, indent + 2)).append("\n");
}
sb.append(pad).append("}");
return sb.toString();
}
Style loadStyle() throws IOException {
return Carto.parse(STYLE);
}
@Override
public synchronized RenderInstruction[] matchElement(GeometryType type, TagSet tags,
int zoomLevel) {
MatcherFeature f = mMatchFeature;
f.setTags(tags);
f.setZoom(zoomLevel);
RuleList rules = mRules.match(f);
Rule r = rules.collapse();
//out.println(r);
if (rules.isEmpty())
return null;
int z = r.number(f, "z", 0f).intValue();
if (type == GeometryType.POLY) {
RGB c = r.color(f, CartoCSS.POLYGON_FILL, RGB.black);
out.println(z + " " + c);
return new RenderInstruction[] {
new Area(z, color(c))
};
} else if (type == GeometryType.LINE) {
RGB c = r.color(f, CartoCSS.LINE_COLOR, RGB.black);
float width = r.number(f, CartoCSS.LINE_WIDTH, 2f);
//out.println(z + " " + c);
return new RenderInstruction[] {
new Line(100 + z, color(c), width)
};
} else if (type == GeometryType.POINT) {
//RGB c = r.color(f, CartoCSS.MARKER_FILL, RGB.black);
//out.println(c);
//return new RenderInstruction[] {
// new Caption(color(c), width)
//};
}
return null;
}
public static int color(RGB rgb) {
return rgb.getAlpha() << 24
| rgb.getRed() << 16
| rgb.getGreen() << 8
| rgb.getBlue();
}
@Override
public void destroy() {
}
@Override
public int getLevels() {
return 1;
}
@Override
public int getMapBackground() {
return mBackground;
}
@Override
public void scaleTextSize(float scaleFactor) {
}
public static void main(String[] args) {
RenderTheme t = new RenderTheme();
MapElement e = new MapElement();
e.startPolygon();
e.tags.add(new Tag("building", "yes"));
t.matchElement(GeometryType.POLY, e.tags, 16);
t.matchElement(GeometryType.POLY, e.tags, 15);
}
}

View File

@ -0,0 +1,74 @@
package org.oscim.theme.carto;
import static java.lang.System.out;
import java.util.Map;
import org.jeo.filter.Filter;
import org.jeo.map.Rule;
import org.jeo.map.Selector;
public class RuleDebug {
static void printRule(Rule r, int level) {
out.println("> " + level + " >");
out.println(formatRule(r, level));
}
public static String formatRule(Rule r, int indent) {
StringBuilder sb = new StringBuilder();
String pad = "";
for (int i = 0; i < indent; i++) {
pad += " ";
};
sb.append(pad);
for (Selector s : r.getSelectors()) {
sb.append(formatSelector(s));
sb.append(",");
}
if (sb.length() > 0) {
sb.setLength(sb.length() - 1);
}
sb.append(pad).append(" {").append("\n");
for (Map.Entry<String, Object> e : r.properties().entrySet()) {
sb.append(pad).append(" ").append(e.getKey()).append(": ").append(e.getValue())
.append(";\n");
}
for (Rule nested : r.nested()) {
sb.append(nested.toString(indent + 2)).append("\n");
}
sb.append(pad).append("}");
return sb.toString();
}
public static String formatSelector(Selector s) {
StringBuffer sb = new StringBuffer();
if (s.getName() != null) {
sb.append(s.getName());
}
if (s.getId() != null) {
sb.append("#").append(s.getId());
}
for (String c : s.getClasses()) {
sb.append(".").append(c);
}
if (s.getFilter() != null && s.getFilter() != Filter.TRUE) {
sb.append("[").append(s.getFilter()).append("]");
}
if (s.getAttachment() != null) {
sb.append("::").append(s.getAttachment());
}
if (s.isWildcard()) {
sb.append("*");
}
return sb.toString();
}
}