From 9109da97840405afd7479c010f8b5ba9439e1a41 Mon Sep 17 00:00:00 2001 From: Hannes Janetzek Date: Tue, 21 Jan 2014 18:19:13 +0100 Subject: [PATCH 1/5] add vtm-jeo --- vtm-jeo/src/org/oscim/layers/JeoMapLayer.java | 80 ++++ .../src/org/oscim/layers/JeoMapLoader.java | 346 ++++++++++++++++++ vtm-jeo/src/org/oscim/layers/JeoTestData.java | 128 +++++++ .../org/oscim/theme/carto/MatcherFeature.java | 74 ++++ .../org/oscim/theme/carto/RenderTheme.java | 266 ++++++++++++++ .../src/org/oscim/theme/carto/RuleDebug.java | 74 ++++ 6 files changed, 968 insertions(+) create mode 100644 vtm-jeo/src/org/oscim/layers/JeoMapLayer.java create mode 100644 vtm-jeo/src/org/oscim/layers/JeoMapLoader.java create mode 100644 vtm-jeo/src/org/oscim/layers/JeoTestData.java create mode 100644 vtm-jeo/src/org/oscim/theme/carto/MatcherFeature.java create mode 100644 vtm-jeo/src/org/oscim/theme/carto/RenderTheme.java create mode 100644 vtm-jeo/src/org/oscim/theme/carto/RuleDebug.java diff --git a/vtm-jeo/src/org/oscim/layers/JeoMapLayer.java b/vtm-jeo/src/org/oscim/layers/JeoMapLayer.java new file mode 100644 index 00000000..083a11f0 --- /dev/null +++ b/vtm-jeo/src/org/oscim/layers/JeoMapLayer.java @@ -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(); + } + +} diff --git a/vtm-jeo/src/org/oscim/layers/JeoMapLoader.java b/vtm-jeo/src/org/oscim/layers/JeoMapLoader.java new file mode 100644 index 00000000..ad7da174 --- /dev/null +++ b/vtm-jeo/src/org/oscim/layers/JeoMapLoader.java @@ -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; + } +} diff --git a/vtm-jeo/src/org/oscim/layers/JeoTestData.java b/vtm-jeo/src/org/oscim/layers/JeoTestData.java new file mode 100644 index 00000000..d2be9c73 --- /dev/null +++ b/vtm-jeo/src/org/oscim/layers/JeoTestData.java @@ -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; + } +} diff --git a/vtm-jeo/src/org/oscim/theme/carto/MatcherFeature.java b/vtm-jeo/src/org/oscim/theme/carto/MatcherFeature.java new file mode 100644 index 00000000..8e345db8 --- /dev/null +++ b/vtm-jeo/src/org/oscim/theme/carto/MatcherFeature.java @@ -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 list() { + out.println("EEEK list()"); + return null; + } + + @Override + public Map 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 + + } + +}; diff --git a/vtm-jeo/src/org/oscim/theme/carto/RenderTheme.java b/vtm-jeo/src/org/oscim/theme/carto/RenderTheme.java new file mode 100644 index 00000000..042a8794 --- /dev/null +++ b/vtm-jeo/src/org/oscim/theme/carto/RenderTheme.java @@ -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 mStyleSets = new HashMap(); + 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 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); + } + +} diff --git a/vtm-jeo/src/org/oscim/theme/carto/RuleDebug.java b/vtm-jeo/src/org/oscim/theme/carto/RuleDebug.java new file mode 100644 index 00000000..ebc92865 --- /dev/null +++ b/vtm-jeo/src/org/oscim/theme/carto/RuleDebug.java @@ -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 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(); + } +} From 3fd92982c4b86123270526f608079d72ead0a250 Mon Sep 17 00:00:00 2001 From: Hannes Janetzek Date: Tue, 21 Jan 2014 18:21:15 +0100 Subject: [PATCH 2/5] add examples --- vtm-jeo-android/AndroidManifest.xml | 30 ++ vtm-jeo-android/project.properties | 15 + vtm-jeo-android/res/drawable/ic_launcher.png | Bin 0 -> 42193 bytes vtm-jeo-android/res/layout/activity_map.xml | 93 +++++ .../org/oscim/jeo/android/TestActivity.java | 162 ++++++++ .../src/org/oscim/jeo/test/LayerTest.java | 27 ++ .../src/org/oscim/jeo/test/ThemeTest.java | 34 ++ vtm-jeo/src/org/oscim/jeo/JeoUtils.java | 12 + vtm-jeo/src/org/oscim/layers/JeoMapLayer.java | 80 ---- .../src/org/oscim/layers/JeoMapLoader.java | 346 ------------------ .../src/org/oscim/layers/JeoTileLayer.java | 35 ++ .../src/org/oscim/layers/JeoTileSource.java | 80 ++++ .../src/org/oscim/layers/JeoVectorLayer.java | 150 ++++++++ vtm-jeo/src/org/oscim/layers/JtsLayer.java | 102 ++++++ .../src/org/oscim/layers/OSMIndoorLayer.java | 141 +++++++ .../JeoTestData.java => test/JeoTest.java} | 69 +++- .../org/oscim/theme/carto/RenderTheme.java | 26 +- .../src/org/oscim/theme/carto/RuleDebug.java | 74 ---- 18 files changed, 956 insertions(+), 520 deletions(-) create mode 100644 vtm-jeo-android/AndroidManifest.xml create mode 100644 vtm-jeo-android/project.properties create mode 100644 vtm-jeo-android/res/drawable/ic_launcher.png create mode 100644 vtm-jeo-android/res/layout/activity_map.xml create mode 100644 vtm-jeo-android/src/org/oscim/jeo/android/TestActivity.java create mode 100644 vtm-jeo-desktop/src/org/oscim/jeo/test/LayerTest.java create mode 100644 vtm-jeo-desktop/src/org/oscim/jeo/test/ThemeTest.java create mode 100644 vtm-jeo/src/org/oscim/jeo/JeoUtils.java delete mode 100644 vtm-jeo/src/org/oscim/layers/JeoMapLayer.java delete mode 100644 vtm-jeo/src/org/oscim/layers/JeoMapLoader.java create mode 100644 vtm-jeo/src/org/oscim/layers/JeoTileLayer.java create mode 100644 vtm-jeo/src/org/oscim/layers/JeoTileSource.java create mode 100644 vtm-jeo/src/org/oscim/layers/JeoVectorLayer.java create mode 100644 vtm-jeo/src/org/oscim/layers/JtsLayer.java create mode 100644 vtm-jeo/src/org/oscim/layers/OSMIndoorLayer.java rename vtm-jeo/src/org/oscim/{layers/JeoTestData.java => test/JeoTest.java} (60%) delete mode 100644 vtm-jeo/src/org/oscim/theme/carto/RuleDebug.java diff --git a/vtm-jeo-android/AndroidManifest.xml b/vtm-jeo-android/AndroidManifest.xml new file mode 100644 index 00000000..d1f57c85 --- /dev/null +++ b/vtm-jeo-android/AndroidManifest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vtm-jeo-android/project.properties b/vtm-jeo-android/project.properties new file mode 100644 index 00000000..b351fa60 --- /dev/null +++ b/vtm-jeo-android/project.properties @@ -0,0 +1,15 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-17 +android.library.reference.1=../../vtm-android diff --git a/vtm-jeo-android/res/drawable/ic_launcher.png b/vtm-jeo-android/res/drawable/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..6496c5adfe925704de11b80a821346331a02dd91 GIT binary patch literal 42193 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 + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vtm-jeo-android/src/org/oscim/jeo/android/TestActivity.java b/vtm-jeo-android/src/org/oscim/jeo/android/TestActivity.java new file mode 100644 index 00000000..8128a707 --- /dev/null +++ b/vtm-jeo-android/src/org/oscim/jeo/android/TestActivity.java @@ -0,0 +1,162 @@ +package org.oscim.jeo.android; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +import org.jeo.data.VectorDataset; +import org.jeo.map.Style; +import org.oscim.android.MapActivity; +import org.oscim.layers.OSMIndoorLayer; +import org.oscim.layers.tile.vector.BuildingLayer; +import org.oscim.layers.tile.vector.VectorTileLayer; +import org.oscim.layers.tile.vector.labeling.LabelLayer; +import org.oscim.renderer.MapRenderer; +import org.oscim.test.JeoTest; +import org.oscim.theme.VtmThemes; +import org.oscim.tiling.source.oscimap4.OSciMap4TileSource; +import org.oscim.utils.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import android.content.Context; +import android.os.Bundle; +import android.os.Environment; +import android.view.View; +import android.widget.Toast; +import android.widget.ToggleButton; + +public class TestActivity extends MapActivity { + public static final Logger log = LoggerFactory.getLogger(TestActivity.class); + + //String PATH = "http://opensciencemap.org/featureserver/featureserver.cgi/osm_indoor"; + + // from http://overpass-turbo.eu/s/2vp + String PATH = "https://gist.github.com/hjanetzek/8959418/raw/overpass.geojson"; + //String PATH = "https://gist.github.com/anonymous/8960337/raw/overpass.geojson"; + + private OSMIndoorLayer mIndoorLayer; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_map); + + MapRenderer.setBackgroundColor(0xff909090); + + mMap.addTask(new Runnable() { + @Override + public void run() { + showToast("load data"); + InputStream is = null; + try { + File file = new File(Environment.getExternalStorageDirectory() + .getAbsolutePath(), "osmindoor.json"); + is = new FileInputStream(file); + + //URL url = new URL(PATH); + //URLConnection conn = url.openConnection(); + //is = conn.getInputStream(); + loadJson(is); + } catch (IOException e) { + e.printStackTrace(); + } finally { + IOUtils.closeQuietly(is); + } + } + }); + + VectorTileLayer baseLayer = mMap.setBaseMap(new OSciMap4TileSource()); + mMap.layers().add(new BuildingLayer(mMap, baseLayer)); + mMap.layers().add(new LabelLayer(mMap, baseLayer)); + mMap.setTheme(VtmThemes.TRON2); + + mMap.setMapPosition(49.417, 8.673, 1 << 17); + // mMap.setMapPosition(53.5620092, 9.9866457, 1 << 16); + + // mMap.layers().add(new TileGridLayer(mMap)); + // String file = Environment.getExternalStorageDirectory().getAbsolutePath(); + // VectorDataset data = (VectorDataset) JeoTest.getJsonData(file + "/states.json", true); + // Style style = JeoTest.getStyle(); + // mMap.layers().add(new JeoVectorLayer(mMap, data, style)); + } + + void loadJson(InputStream is) { + showToast("got data"); + + VectorDataset data = JeoTest.readGeoJson(is); + Style style = JeoTest.getStyle(); + mIndoorLayer = new OSMIndoorLayer(mMap, data, style); + mMap.layers().add(mIndoorLayer); + + showToast("data ready"); + mMap.updateMap(true); + + mIndoorLayer.activeLevels[0] = true; + shift(); + } + + public void showToast(final String text) { + final Context ctx = this; + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast toast = Toast.makeText(ctx, text, Toast.LENGTH_SHORT); + toast.show(); + } + }); + } + + boolean mShift = true; + + public void shift() { + if (!mShift) + return; + + mMap.postDelayed(new Runnable() { + + @Override + public void run() { + for (int i = 0; i < 10; i++) { + if (mIndoorLayer.activeLevels[i]) { + mIndoorLayer.activeLevels[i] = false; + mIndoorLayer.activeLevels[(i + 1) % 9] = true; + mIndoorLayer.update(); + break; + } + } + shift(); + } + }, 200); + + } + + public void onClick(View v) { + mShift = false; + + if (mIndoorLayer == null) + return; + + int i = 0; + + if (v instanceof ToggleButton) { + ToggleButton b = (ToggleButton) v; + i = (b.getTextOn().charAt(0) - '0') + 1; + } + + if (i < 0 || i > 9) + i = 0; + + mIndoorLayer.activeLevels[i] ^= true; + ((ToggleButton) v).setChecked(mIndoorLayer.activeLevels[i]); + log.debug(Arrays.toString(mIndoorLayer.activeLevels)); + mIndoorLayer.update(); + } + + @Override + protected void onStop() { + super.onStop(); + } +} diff --git a/vtm-jeo-desktop/src/org/oscim/jeo/test/LayerTest.java b/vtm-jeo-desktop/src/org/oscim/jeo/test/LayerTest.java new file mode 100644 index 00000000..5fe5a2ed --- /dev/null +++ b/vtm-jeo-desktop/src/org/oscim/jeo/test/LayerTest.java @@ -0,0 +1,27 @@ +package org.oscim.jeo.test; + +import org.jeo.data.VectorDataset; +import org.jeo.map.Style; +import org.oscim.gdx.GdxMap; +import org.oscim.gdx.GdxMapApp; +import org.oscim.layers.JeoVectorLayer; +import org.oscim.test.JeoTest; + +public class LayerTest extends GdxMap { + + @Override + public void createLayers() { + //JeoTest.indoorSketch(mMap, "osmindoor.json"); + //mMap.setMapPosition(49.417, 8.673, 1 << 17); + + VectorDataset data = (VectorDataset) JeoTest.getJsonData("states.json", true); + Style style = JeoTest.getStyle(); + + mMap.layers().add(new JeoVectorLayer(mMap, data, style)); + } + + public static void main(String[] args) { + GdxMapApp.init(); + GdxMapApp.run(new LayerTest(), null, 256); + } +} diff --git a/vtm-jeo-desktop/src/org/oscim/jeo/test/ThemeTest.java b/vtm-jeo-desktop/src/org/oscim/jeo/test/ThemeTest.java new file mode 100644 index 00000000..57eff695 --- /dev/null +++ b/vtm-jeo-desktop/src/org/oscim/jeo/test/ThemeTest.java @@ -0,0 +1,34 @@ +package org.oscim.jeo.test; + +import org.oscim.gdx.GdxMapApp; +import org.oscim.layers.TileGridLayer; +import org.oscim.layers.tile.vector.VectorTileLayer; +import org.oscim.renderer.MapRenderer; +import org.oscim.theme.carto.RenderTheme; +import org.oscim.tiling.source.UrlTileSource; +import org.oscim.tiling.source.oscimap4.OSciMap4TileSource; + +public class ThemeTest extends GdxMapApp { + + public static void main(String[] args) { + GdxMapApp.init(); + GdxMapApp.run(new ThemeTest(), null, 256); + } + + @Override + public void createLayers() { + UrlTileSource ts = new OSciMap4TileSource(); + + VectorTileLayer l = mMap.setBaseMap(ts); + + l.setRenderTheme(new RenderTheme()); + + MapRenderer.setBackgroundColor(0xffcccccc); + + // mMap.getLayers().add(new LabelLayer(mMap, + // mMapLayer.getTileLayer())); + // mMap.getLayers().add(new JeoMapLayer(mMap)); + + mMap.layers().add(new TileGridLayer(mMap)); + } +} diff --git a/vtm-jeo/src/org/oscim/jeo/JeoUtils.java b/vtm-jeo/src/org/oscim/jeo/JeoUtils.java new file mode 100644 index 00000000..0395d589 --- /dev/null +++ b/vtm-jeo/src/org/oscim/jeo/JeoUtils.java @@ -0,0 +1,12 @@ +package org.oscim.jeo; + +import org.jeo.map.RGB; + +public class JeoUtils { + public static int color(RGB rgb) { + return rgb.getAlpha() << 24 + | rgb.getRed() << 16 + | rgb.getGreen() << 8 + | rgb.getBlue(); + } +} diff --git a/vtm-jeo/src/org/oscim/layers/JeoMapLayer.java b/vtm-jeo/src/org/oscim/layers/JeoMapLayer.java deleted file mode 100644 index 083a11f0..00000000 --- a/vtm-jeo/src/org/oscim/layers/JeoMapLayer.java +++ /dev/null @@ -1,80 +0,0 @@ -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(); - } - -} diff --git a/vtm-jeo/src/org/oscim/layers/JeoMapLoader.java b/vtm-jeo/src/org/oscim/layers/JeoMapLoader.java deleted file mode 100644 index ad7da174..00000000 --- a/vtm-jeo/src/org/oscim/layers/JeoMapLoader.java +++ /dev/null @@ -1,346 +0,0 @@ -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; - } -} diff --git a/vtm-jeo/src/org/oscim/layers/JeoTileLayer.java b/vtm-jeo/src/org/oscim/layers/JeoTileLayer.java new file mode 100644 index 00000000..f9162dc9 --- /dev/null +++ b/vtm-jeo/src/org/oscim/layers/JeoTileLayer.java @@ -0,0 +1,35 @@ +package org.oscim.layers; + +import org.oscim.layers.tile.MapTile; +import org.oscim.layers.tile.TileLoader; +import org.oscim.layers.tile.TileManager; +import org.oscim.layers.tile.bitmap.BitmapTileLayer; +import org.oscim.map.Map; +import org.oscim.tiling.source.bitmap.BitmapTileSource; + +public class JeoTileLayer extends BitmapTileLayer { + + public JeoTileLayer(Map map, BitmapTileSource tileSource) { + super(map, tileSource); + } + + @Override + protected TileLoader createLoader(TileManager tm) { + return new TileLoader(tm) { + + @Override + public void cleanup() { + // TODO Auto-generated method stub + + } + + @Override + protected boolean executeJob(MapTile tile) { + // TODO Auto-generated method stub + return false; + } + + }; + } + +} diff --git a/vtm-jeo/src/org/oscim/layers/JeoTileSource.java b/vtm-jeo/src/org/oscim/layers/JeoTileSource.java new file mode 100644 index 00000000..0648e10c --- /dev/null +++ b/vtm-jeo/src/org/oscim/layers/JeoTileSource.java @@ -0,0 +1,80 @@ +package org.oscim.layers; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import org.jeo.data.Tile; +import org.jeo.data.TileDataset; +import org.oscim.backend.CanvasAdapter; +import org.oscim.backend.canvas.Bitmap; +import org.oscim.layers.tile.MapTile; +import org.oscim.tiling.ITileDataSink; +import org.oscim.tiling.ITileDataSource; +import org.oscim.tiling.TileSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JeoTileSource extends TileSource { + final static Logger log = LoggerFactory.getLogger(JeoTileSource.class); + + final TileDataset mTileDataset; + + public JeoTileSource(TileDataset tileDataset) { + log.debug("load tileset {}", tileDataset.getName()); + mTileDataset = tileDataset; + //mTileDataset.pyramid(). + mZoomMax = 1; + mZoomMin = 0; + } + + @Override + public ITileDataSource getDataSource() { + return new ITileDataSource() { + + @Override + public QueryResult executeQuery(MapTile tile, ITileDataSink sink) { + log.debug("query {}", tile); + try { + Tile t = mTileDataset.read(tile.zoomLevel, tile.tileX, + // flip Y axis + (1 << tile.zoomLevel) - 1 - tile.tileY); + if (t == null) { + log.debug("not found {}", tile); + return QueryResult.TILE_NOT_FOUND; + } + Bitmap b = CanvasAdapter.g.decodeBitmap(new ByteArrayInputStream(t.getData())); + sink.setTileImage(b); + log.debug("success {}", tile); + + return QueryResult.SUCCESS; + + } catch (IOException e) { + e.printStackTrace(); + } + log.debug("fail {}", tile); + + return QueryResult.FAILED; + } + + @Override + public void destroy() { + + } + }; + } + + int mRefs; + + @Override + public OpenResult open() { + mRefs++; + return OpenResult.SUCCESS; + } + + @Override + public void close() { + if (--mRefs == 0) + mTileDataset.close(); + } + +} diff --git a/vtm-jeo/src/org/oscim/layers/JeoVectorLayer.java b/vtm-jeo/src/org/oscim/layers/JeoVectorLayer.java new file mode 100644 index 00000000..8f47e9ec --- /dev/null +++ b/vtm-jeo/src/org/oscim/layers/JeoVectorLayer.java @@ -0,0 +1,150 @@ +package org.oscim.layers; + +import java.io.IOException; + +import org.jeo.data.Query; +import org.jeo.data.VectorDataset; +import org.jeo.feature.Feature; +import org.jeo.geom.Geom; +import org.jeo.map.CartoCSS; +import org.jeo.map.RGB; +import org.jeo.map.Rule; +import org.jeo.map.RuleList; +import org.jeo.map.Style; +import org.oscim.jeo.JeoUtils; +import org.oscim.map.Map; +import org.oscim.renderer.elements.LineLayer; +import org.oscim.renderer.elements.MeshLayer; +import org.oscim.theme.styles.Area; +import org.oscim.theme.styles.Line; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.vividsolutions.jts.geom.Envelope; +import com.vividsolutions.jts.geom.Geometry; +import com.vividsolutions.jts.geom.LineString; + +public class JeoVectorLayer extends JtsLayer { + + public static final Logger log = LoggerFactory.getLogger(JeoVectorLayer.class); + static final boolean dbg = false; + + private final VectorDataset mDataset; + private final RuleList mRules; + + protected double mDropPointDistance = 0.01; + + public JeoVectorLayer(Map map, VectorDataset data, Style style) { + super(map); + mDataset = data; + + mRules = style.getRules().selectById(data.getName(), true).flatten(); + //mRules = style.getRules().selectById("way", true).flatten(); + log.debug(mRules.toString()); + + mRenderer = new Renderer(); + } + + @Override + protected void processFeatures(Task t, Envelope b) { + if (mDropPointDistance > 0) { + /* reduce lines points min distance */ + mMinX = ((b.getMaxX() - b.getMinX()) / mMap.getWidth()); + mMinY = ((b.getMaxY() - b.getMinY()) / mMap.getHeight()); + mMinX *= mDropPointDistance; + mMinY *= mDropPointDistance; + } + + try { + Query q = new Query().bounds(b); + if (dbg) + log.debug("query {}", b); + for (Feature f : mDataset.cursor(q)) { + if (dbg) + log.debug("feature {}", f); + + RuleList rs = mRules.match(f); + if (rs.isEmpty()) + continue; + + Rule r = rs.collapse(); + if (r == null) + continue; + + Geometry g = f.geometry(); + if (g == null) + continue; + + switch (Geom.Type.from(g)) { + case POINT: + addPoint(t, f, r, g); + break; + case MULTIPOINT: + for (int i = 0, n = g.getNumGeometries(); i < n; i++) + addPoint(t, f, r, g.getGeometryN(i)); + break; + case LINESTRING: + addLine(t, f, r, g); + break; + case MULTILINESTRING: + for (int i = 0, n = g.getNumGeometries(); i < n; i++) + addLine(t, f, r, g.getGeometryN(i)); + break; + case POLYGON: + addPolygon(t, f, r, g); + break; + case MULTIPOLYGON: + for (int i = 0, n = g.getNumGeometries(); i < n; i++) + addPolygon(t, f, r, g.getGeometryN(i)); + break; + default: + break; + } + } + } catch (IOException e) { + log.error("Error querying layer " + mDataset.getName() + e); + } + } + + protected void addLine(Task t, Feature f, Rule rule, Geometry g) { + + if (((LineString) g).isClosed()) { + addPolygon(t, f, rule, g); + return; + } + + LineLayer ll = t.layers.getLineLayer(2); + 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, JeoUtils.color(color), width); + ll.setDropDistance(0.5f); + } + + addLine(t, g, ll); + } + + protected void addPolygon(Task t, Feature f, Rule rule, Geometry g) { + + LineLayer ll = t.layers.getLineLayer(1); + + if (ll.line == null) { + float width = rule.number(f, CartoCSS.LINE_WIDTH, 1.2f); + RGB color = rule.color(f, CartoCSS.LINE_COLOR, RGB.black); + ll.line = new Line(0, JeoUtils.color(color), width); + ll.setDropDistance(0.5f); + } + + MeshLayer mesh = t.layers.getMeshLayer(0); + if (mesh.area == null) { + int color = JeoUtils.color(rule.color(f, CartoCSS.POLYGON_FILL, RGB.red)); + mesh.area = new Area(color); + } + + addPolygon(t, g, mesh, ll); + } + + protected void addPoint(Task t, Feature f, Rule rule, Geometry g) { + + } +} diff --git a/vtm-jeo/src/org/oscim/layers/JtsLayer.java b/vtm-jeo/src/org/oscim/layers/JtsLayer.java new file mode 100644 index 00000000..6a1cf5e3 --- /dev/null +++ b/vtm-jeo/src/org/oscim/layers/JtsLayer.java @@ -0,0 +1,102 @@ +package org.oscim.layers; + +import org.jeo.geom.CoordinatePath; +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.layers.vector.AbstractVectorLayer; +import org.oscim.map.Map; +import org.oscim.renderer.elements.LineLayer; +import org.oscim.renderer.elements.MeshLayer; +import org.oscim.utils.geom.SimplifyDP; +import org.oscim.utils.geom.SimplifyVW; + +import com.vividsolutions.jts.geom.Coordinate; +import com.vividsolutions.jts.geom.Envelope; +import com.vividsolutions.jts.geom.Geometry; + +public abstract class JtsLayer extends AbstractVectorLayer { + + public JtsLayer(Map map) { + super(map); + } + + @Override + protected void processFeatures(Task t, BoundingBox bbox) { + processFeatures(t, new Envelope(bbox.getMinLongitude(), bbox.getMaxLongitude(), + bbox.getMinLatitude(), bbox.getMaxLatitude())); + + } + + protected abstract void processFeatures(Task t, Envelope e); + + protected int transformPath(MapPosition pos, GeometryBuffer g, CoordinatePath path) { + + double scale = pos.scale * Tile.SIZE / UNSCALE_COORD; + 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; + } + + SimplifyDP mSimpDP = new SimplifyDP(); + SimplifyVW mSimpVW = new SimplifyVW(); + + protected void addPolygon(Task t, Geometry g, MeshLayer ml, LineLayer ll) { + mGeom.clear(); + mGeom.startPolygon(); + + CoordinatePath p = CoordinatePath.create(g); + if (mMinX > 0 || mMinY > 0) + p.generalize(mMinX, mMinY); + + if (transformPath(t.position, mGeom, p) < 3) + return; + + if (!mClipper.clip(mGeom)) + return; + + mSimpVW.simplify(mGeom, 0.1f); + mSimpDP.simplify(mGeom, 0.5f); + + ll.addLine(mGeom); + ml.addMesh(mGeom); + } + + protected void addLine(Task t, Geometry g, LineLayer ll) { + mGeom.clear(); + mGeom.startLine(); + + CoordinatePath p = CoordinatePath.create(g); + transformPath(t.position, mGeom, p); + + ll.addLine(mGeom); + } +} diff --git a/vtm-jeo/src/org/oscim/layers/OSMIndoorLayer.java b/vtm-jeo/src/org/oscim/layers/OSMIndoorLayer.java new file mode 100644 index 00000000..e1f8d4c0 --- /dev/null +++ b/vtm-jeo/src/org/oscim/layers/OSMIndoorLayer.java @@ -0,0 +1,141 @@ +package org.oscim.layers; + +import java.util.HashMap; + +import org.jeo.data.VectorDataset; +import org.jeo.feature.Feature; +import org.jeo.map.CartoCSS; +import org.jeo.map.RGB; +import org.jeo.map.Rule; +import org.jeo.map.Style; +import org.oscim.backend.canvas.Color; +import org.oscim.jeo.JeoUtils; +import org.oscim.map.Map; +import org.oscim.renderer.elements.LineLayer; +import org.oscim.renderer.elements.MeshLayer; +import org.oscim.renderer.elements.TextItem; +import org.oscim.renderer.elements.TextLayer; +import org.oscim.theme.styles.Area; +import org.oscim.theme.styles.Line; +import org.oscim.theme.styles.Text; + +import com.vividsolutions.jts.geom.Envelope; +import com.vividsolutions.jts.geom.Geometry; +import com.vividsolutions.jts.geom.LineString; + +public class OSMIndoorLayer extends JeoVectorLayer { + + protected TextLayer mTextLayer; + protected Text mText = Text.createText(16, 2.2f, Color.BLACK, Color.WHITE, true); + + public OSMIndoorLayer(Map map, VectorDataset data, Style style) { + super(map, data, style); + } + + public boolean[] activeLevels = new boolean[10]; + + @Override + protected void processFeatures(Task t, Envelope b) { + mTextLayer = t.layers.addTextLayer(new TextLayer()); + + super.processFeatures(t, b); + + //render TextItems to a bitmap and prepare vertex buffer data. + mTextLayer.prepare(); + mTextLayer.clearLabels(); + } + + protected void addLine(Task t, Feature f, Rule rule, Geometry g) { + + if (((LineString) g).isClosed()) { + addPolygon(t, f, rule, g); + return; + } + + int level = getLevel(f); + + LineLayer ll = t.layers.getLineLayer(level * 3 + 2); + 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, JeoUtils.color(color), width); + ll.heightOffset = level * 4; + ll.setDropDistance(0); + } + + addLine(t, g, ll); + } + + protected void addPolygon(Task t, Feature f, Rule rule, Geometry g) { + int level = getLevel(f); + + LineLayer ll = t.layers.getLineLayer(level * 3 + 1); + + if (ll.line == null) { + float width = rule.number(f, CartoCSS.LINE_WIDTH, 1.2f); + int color = Color.rainbow((level + 1) / 10f); + + if (level > -2 && !activeLevels[level + 1]) + color = Color.fade(color, 0.1f); + + ll.line = new Line(0, color, width); + ll.heightOffset = level * 4; + ll.setDropDistance(0); + } + + MeshLayer mesh = t.layers.getMeshLayer(level * 3); + if (mesh.area == null) { + int color = JeoUtils.color(rule.color(f, CartoCSS.POLYGON_FILL, RGB.red)); + if (level > -2 && !activeLevels[level + 1]) + color = Color.fade(color, 0.1f); + + mesh.area = new Area(color); + //mesh.area = new Area(Color.fade(Color.DKGRAY, 0.1f)); + mesh.heightOffset = level * 4f; + } + + addPolygon(t, g, mesh, ll); + + Object o = f.get("name"); + if (o instanceof String) { + float x = 0; + float y = 0; + int n = mGeom.index[0]; + for (int i = 0; i < n;) { + x += mGeom.points[i++]; + y += mGeom.points[i++]; + } + + TextItem ti = TextItem.pool.get(); + ti.set(x / (n / 2) / 8, y / (n / 2) / 8, (String) o, mText); + + mTextLayer.addText(ti); + } + } + + @Override + protected void addPoint(Task t, Feature f, Rule rule, Geometry g) { + + } + + private int getLevel(Feature f) { + /* not sure if one could match these geojson properties with cartocss */ + Object o = f.get("@relations"); + if (o instanceof HashMap) { + @SuppressWarnings("unchecked") + HashMap tags = (HashMap) o; + @SuppressWarnings("unchecked") + HashMap reltags = (HashMap) tags.get("reltags"); + + if (reltags != null) { + o = reltags.get("level"); + if (o instanceof String) { + //log.debug("got level {}", o); + return Integer.parseInt((String) o); + } + } + } + return 0; + } + +} diff --git a/vtm-jeo/src/org/oscim/layers/JeoTestData.java b/vtm-jeo/src/org/oscim/test/JeoTest.java similarity index 60% rename from vtm-jeo/src/org/oscim/layers/JeoTestData.java rename to vtm-jeo/src/org/oscim/test/JeoTest.java index d2be9c73..d29955a3 100644 --- a/vtm-jeo/src/org/oscim/layers/JeoTestData.java +++ b/vtm-jeo/src/org/oscim/test/JeoTest.java @@ -1,11 +1,15 @@ -package org.oscim.layers; +package org.oscim.test; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; import org.jeo.carto.Carto; import org.jeo.data.Dataset; import org.jeo.data.Query; +import org.jeo.data.VectorDataset; import org.jeo.data.mem.MemVector; import org.jeo.data.mem.MemWorkspace; import org.jeo.feature.Feature; @@ -13,24 +17,52 @@ import org.jeo.feature.Features; import org.jeo.feature.Schema; import org.jeo.feature.SchemaBuilder; import org.jeo.geojson.GeoJSONDataset; +import org.jeo.geojson.GeoJSONReader; import org.jeo.geom.GeomBuilder; import org.jeo.map.Style; +import org.oscim.layers.OSMIndoorLayer; +import org.oscim.layers.tile.vector.BuildingLayer; +import org.oscim.layers.tile.vector.VectorTileLayer; +import org.oscim.map.Map; +import org.oscim.renderer.MapRenderer; +import org.oscim.tiling.source.oscimap4.OSciMap4TileSource; import com.vividsolutions.jts.geom.Geometry; -public class JeoTestData { +public class JeoTest { + + public static void indoorSketch(Map map, String file) { + MapRenderer.setBackgroundColor(0xff909090); + VectorTileLayer baseLayer = map.setBaseMap(new OSciMap4TileSource()); + map.layers().add(new BuildingLayer(map, baseLayer)); + + VectorDataset data = null; + try { + data = JeoTest.readGeoJson(new FileInputStream(new File(file))); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + + Style style = JeoTest.getStyle(); + map.layers().add(new OSMIndoorLayer(map, data, style)); + } public static Style getStyle() { Style style = null; try { style = Carto.parse("" + - "#things {" + + "#way {" + + " line-width: 2;" + " line-color: #c80;" + - " polygon-fill: #00a;" + + " polygon-fill: #44111111;" + + " " + "}" + "#states {" + - " polygon-fill: #0dc;" + + " line-width: 2.2;" + + " line-color: #c80;" + + " polygon-fill: #44111111;" + + " " + "}" ); @@ -41,6 +73,29 @@ public class JeoTestData { return null; } + public static VectorDataset readGeoJson(InputStream is) { + GeoJSONReader r = new GeoJSONReader(); + + @SuppressWarnings("resource") + MemWorkspace mem = new MemWorkspace(); + + //mem.put("layer", data); + try { + Schema s = new SchemaBuilder("way").schema(); + + MemVector memData = mem.create(s); + + for (Feature f : r.features(is)) { + //System.out.println("loaded: " + f); + memData.add(f); + } + return memData; + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + public static Dataset getJsonData(String file, boolean memory) { GeoJSONDataset data = null; @@ -53,6 +108,7 @@ public class JeoTestData { } if (memory) { + @SuppressWarnings("resource") MemWorkspace mem = new MemWorkspace(); //mem.put("layer", data); @@ -79,6 +135,7 @@ public class JeoTestData { public static Dataset getMemWorkspace(String layer) { GeomBuilder gb = new GeomBuilder(4326); + @SuppressWarnings("resource") MemWorkspace mem = new MemWorkspace(); Schema schema = new SchemaBuilder(layer) .field("geometry", Geometry.class) @@ -90,11 +147,9 @@ public class JeoTestData { 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; } diff --git a/vtm-jeo/src/org/oscim/theme/carto/RenderTheme.java b/vtm-jeo/src/org/oscim/theme/carto/RenderTheme.java index 042a8794..92c7f398 100644 --- a/vtm-jeo/src/org/oscim/theme/carto/RenderTheme.java +++ b/vtm-jeo/src/org/oscim/theme/carto/RenderTheme.java @@ -13,16 +13,15 @@ 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; +import org.oscim.theme.styles.Area; +import org.oscim.theme.styles.Line; +import org.oscim.theme.styles.RenderStyle; public class RenderTheme implements IRenderTheme { @@ -117,7 +116,7 @@ public class RenderTheme implements IRenderTheme { class StyleSet { int level; - RenderInstruction[] ri = new RenderInstruction[2]; + RenderStyle[] ri = new RenderStyle[2]; } Map mStyleSets = new HashMap(); @@ -132,11 +131,6 @@ public class RenderTheme implements IRenderTheme { sb.append(pad); - for (Selector s : r.getSelectors()) { - sb.append(RuleDebug.formatSelector(s)); - sb.append(","); - } - if (sb.length() > 0) sb.setLength(sb.length() - 1); @@ -183,7 +177,7 @@ public class RenderTheme implements IRenderTheme { } @Override - public synchronized RenderInstruction[] matchElement(GeometryType type, TagSet tags, + public synchronized RenderStyle[] matchElement(GeometryType type, TagSet tags, int zoomLevel) { MatcherFeature f = mMatchFeature; @@ -203,7 +197,7 @@ public class RenderTheme implements IRenderTheme { if (type == GeometryType.POLY) { RGB c = r.color(f, CartoCSS.POLYGON_FILL, RGB.black); out.println(z + " " + c); - return new RenderInstruction[] { + return new RenderStyle[] { new Area(z, color(c)) }; @@ -212,7 +206,7 @@ public class RenderTheme implements IRenderTheme { float width = r.number(f, CartoCSS.LINE_WIDTH, 2f); //out.println(z + " " + c); - return new RenderInstruction[] { + return new RenderStyle[] { new Line(100 + z, color(c), width) }; @@ -263,4 +257,10 @@ public class RenderTheme implements IRenderTheme { t.matchElement(GeometryType.POLY, e.tags, 15); } + @Override + public void updateInstructions() { + // TODO Auto-generated method stub + + } + } diff --git a/vtm-jeo/src/org/oscim/theme/carto/RuleDebug.java b/vtm-jeo/src/org/oscim/theme/carto/RuleDebug.java deleted file mode 100644 index ebc92865..00000000 --- a/vtm-jeo/src/org/oscim/theme/carto/RuleDebug.java +++ /dev/null @@ -1,74 +0,0 @@ -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 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(); - } -} From a480ef4b8bcaa7372a511f60b209ff1570e87869 Mon Sep 17 00:00:00 2001 From: Hannes Janetzek Date: Sat, 8 Feb 2014 18:19:17 +0100 Subject: [PATCH 3/5] update target sdk --- vtm-jeo-android/AndroidManifest.xml | 4 ++-- vtm-jeo-android/project.properties | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vtm-jeo-android/AndroidManifest.xml b/vtm-jeo-android/AndroidManifest.xml index d1f57c85..7dff7e16 100644 --- a/vtm-jeo-android/AndroidManifest.xml +++ b/vtm-jeo-android/AndroidManifest.xml @@ -9,7 +9,7 @@ + android:targetSdkVersion="19" /> - \ No newline at end of file + diff --git a/vtm-jeo-android/project.properties b/vtm-jeo-android/project.properties index b351fa60..1a75492e 100644 --- a/vtm-jeo-android/project.properties +++ b/vtm-jeo-android/project.properties @@ -11,5 +11,5 @@ #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. -target=android-17 +target=android-19 android.library.reference.1=../../vtm-android From bb439236d60518d89dd20a4fa7ef32f820c0e212 Mon Sep 17 00:00:00 2001 From: Hannes Janetzek Date: Fri, 28 Feb 2014 20:42:43 +0100 Subject: [PATCH 4/5] add gradle --- settings.gradle | 3 ++ vtm-jeo-android/build.gradle | 91 ++++++++++++++++++++++++++++++++++++ vtm-jeo-desktop/build.gradle | 22 +++++++++ vtm-jeo/build.gradle | 27 +++++++++++ 4 files changed, 143 insertions(+) create mode 100644 vtm-jeo-android/build.gradle create mode 100644 vtm-jeo-desktop/build.gradle create mode 100644 vtm-jeo/build.gradle diff --git a/settings.gradle b/settings.gradle index 93f56fc4..0c12edc5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -9,3 +9,6 @@ include ':vtm-gdx-desktop' include ':vtm-gdx-android' include ':vtm-gdx-html' include ':vtm-gdx-ios' +include ':vtm-jeo' +include ':vtm-jeo-desktop' +include ':vtm-jeo-android' diff --git a/vtm-jeo-android/build.gradle b/vtm-jeo-android/build.gradle new file mode 100644 index 00000000..0cac9e9f --- /dev/null +++ b/vtm-jeo-android/build.gradle @@ -0,0 +1,91 @@ +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:0.8.+' + } +} + +apply plugin: 'android' + +dependencies { + compile project(':vtm-jeo') + compile 'org.oscim:vtm-android:0.5.9-SNAPSHOT' + compile 'org.oscim:vtm-themes:0.5.9-SNAPSHOT' +} + +android { + compileSdkVersion 19 + buildToolsVersion '19.0.1' + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + resources.srcDirs = ['src', 'assets'] + res.srcDirs = ['res'] + assets.srcDirs = ['assets'] + } + + debug.setRoot('build-types/debug') + release.setRoot('build-types/release') + } + + packagingOptions { + //exclude 'META-INF/**/*' + exclude 'META-INF/services/org.jeo.data.Driver' + } + // ignore deprecated + lintOptions.abortOnError false +} + +// Including configurations into Eclipse +eclipse { + sourceCompatibility = 1.6 + targetCompatibility = 1.6 + + // Configuring Eclipse classpath + classpath { + plusConfigurations += configurations.compile + + defaultOutputDir = file('bin/classes') + + file { + // Direct manipulation of the generated classpath XML + withXml { + def node = it.asNode() + node.appendNode('classpathentry kind="src" path="src"') + node.appendNode('classpathentry kind="src" path="gen"') + } + + whenMerged { classpath -> + classpath.entries.removeAll { entry -> + entry.path.contains('vtm-android-0.5.9') } + classpath.entries.removeAll { entry -> + entry.path.contains('vtm-0.5.9') } + } + } + } + + project { + natures = ['com.android.ide.eclipse.adt.AndroidNature', + 'org.eclipse.jdt.core.javanature'] + + buildCommand 'com.android.ide.eclipse.adt.ResourceManagerBuilder' + buildCommand 'com.android.ide.eclipse.adt.PreCompilerBuilder' + buildCommand 'com.android.ide.eclipse.adt.ApkBuilder' + } +} + +task run (dependsOn: 'installDebug'){ + doFirst { + println(">> adb run...") + String adb = System.getenv()['ANDROID_HOME'] + '/platform-tools/adb' + String cmd = "${adb} shell am start -n org.oscim.jeo.android/.TestActivity" + def proc = cmd.execute() + proc.in.eachLine {line -> println line} + proc.err.eachLine {line -> System.err.println( 'ERROR: ' + line)} + proc.waitFor() + } +} \ No newline at end of file diff --git a/vtm-jeo-desktop/build.gradle b/vtm-jeo-desktop/build.gradle new file mode 100644 index 00000000..dd104bcc --- /dev/null +++ b/vtm-jeo-desktop/build.gradle @@ -0,0 +1,22 @@ +repositories { + // libgdx + maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } +} + +apply plugin: 'java' +apply plugin: 'maven' +apply plugin: 'application' + +mainClassName = 'org.oscim.jeo.test.LayerTest' + +sourceSets { + main.java.srcDirs = ['src'] + main.resources.srcDirs = ['resources'] +} + +dependencies { + compile project(':vtm-jeo') + compile 'org.oscim:vtm-gdx-desktop:0.5.9-SNAPSHOT' +} + +run { ignoreExitValue = true } diff --git a/vtm-jeo/build.gradle b/vtm-jeo/build.gradle new file mode 100644 index 00000000..5e980a98 --- /dev/null +++ b/vtm-jeo/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'java' +apply plugin: 'maven' + +repositories { + mavenCentral() + mavenLocal() +} + +sourceSets { + main.java.srcDirs = ['src'] + main.resources.srcDirs = ['src'] +} + +dependencies { + compile 'org.oscim:vtm:0.5.9-SNAPSHOT' + compile ('org.jeo:jeo:0-SNAPSHOT') { + exclude group: 'org.slf4j', module: 'slf4j-jdk14' + } + compile ('org.jeo:jeo-carto:0-SNAPSHOT') { + exclude group: 'org.slf4j', module: 'slf4j-jdk14' + } +} + +eclipse.classpath.file.whenMerged { classpath -> + classpath.entries.findAll { entry -> + entry.path.contains('vtm-0.5.9-SNAPSHOT.jar') }*.exported = false +} From 2c133a87cbf108959dfe970932525214e1064591 Mon Sep 17 00:00:00 2001 From: Hannes Janetzek Date: Fri, 28 Feb 2014 23:43:43 +0100 Subject: [PATCH 5/5] update Indoor example --- .../org/oscim/jeo/android/TestActivity.java | 27 +++++++-------- .../src/org/oscim/layers/OSMIndoorLayer.java | 34 +++++++++++-------- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/vtm-jeo-android/src/org/oscim/jeo/android/TestActivity.java b/vtm-jeo-android/src/org/oscim/jeo/android/TestActivity.java index 8128a707..5a40cd79 100644 --- a/vtm-jeo-android/src/org/oscim/jeo/android/TestActivity.java +++ b/vtm-jeo-android/src/org/oscim/jeo/android/TestActivity.java @@ -1,9 +1,9 @@ package org.oscim.jeo.android; -import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; import java.util.Arrays; import org.jeo.data.VectorDataset; @@ -23,7 +23,6 @@ import org.slf4j.LoggerFactory; import android.content.Context; import android.os.Bundle; -import android.os.Environment; import android.view.View; import android.widget.Toast; import android.widget.ToggleButton; @@ -31,11 +30,9 @@ import android.widget.ToggleButton; public class TestActivity extends MapActivity { public static final Logger log = LoggerFactory.getLogger(TestActivity.class); - //String PATH = "http://opensciencemap.org/featureserver/featureserver.cgi/osm_indoor"; - // from http://overpass-turbo.eu/s/2vp - String PATH = "https://gist.github.com/hjanetzek/8959418/raw/overpass.geojson"; - //String PATH = "https://gist.github.com/anonymous/8960337/raw/overpass.geojson"; + String PATH = "https://gist.github.com/anonymous/8960337/raw/overpass.geojson"; + //String PATH = "https://gist.github.com/hjanetzek/9280925/raw/overpass.geojson"; private OSMIndoorLayer mIndoorLayer; @@ -52,13 +49,13 @@ public class TestActivity extends MapActivity { showToast("load data"); InputStream is = null; try { - File file = new File(Environment.getExternalStorageDirectory() - .getAbsolutePath(), "osmindoor.json"); - is = new FileInputStream(file); + //File file = new File(Environment.getExternalStorageDirectory() + // .getAbsolutePath(), "osmindoor.json"); + //is = new FileInputStream(file); - //URL url = new URL(PATH); - //URLConnection conn = url.openConnection(); - //is = conn.getInputStream(); + URL url = new URL(PATH); + URLConnection conn = url.openConnection(); + is = conn.getInputStream(); loadJson(is); } catch (IOException e) { e.printStackTrace(); @@ -73,8 +70,8 @@ public class TestActivity extends MapActivity { mMap.layers().add(new LabelLayer(mMap, baseLayer)); mMap.setTheme(VtmThemes.TRON2); - mMap.setMapPosition(49.417, 8.673, 1 << 17); - // mMap.setMapPosition(53.5620092, 9.9866457, 1 << 16); + //mMap.setMapPosition(49.417, 8.673, 1 << 17); + mMap.setMapPosition(53.5620092, 9.9866457, 1 << 16); // mMap.layers().add(new TileGridLayer(mMap)); // String file = Environment.getExternalStorageDirectory().getAbsolutePath(); diff --git a/vtm-jeo/src/org/oscim/layers/OSMIndoorLayer.java b/vtm-jeo/src/org/oscim/layers/OSMIndoorLayer.java index e1f8d4c0..11139ea8 100644 --- a/vtm-jeo/src/org/oscim/layers/OSMIndoorLayer.java +++ b/vtm-jeo/src/org/oscim/layers/OSMIndoorLayer.java @@ -71,11 +71,13 @@ public class OSMIndoorLayer extends JeoVectorLayer { LineLayer ll = t.layers.getLineLayer(level * 3 + 1); + boolean active = activeLevels[level + 1]; + if (ll.line == null) { float width = rule.number(f, CartoCSS.LINE_WIDTH, 1.2f); int color = Color.rainbow((level + 1) / 10f); - if (level > -2 && !activeLevels[level + 1]) + if (level > -2 && !active) color = Color.fade(color, 0.1f); ll.line = new Line(0, color, width); @@ -86,7 +88,7 @@ public class OSMIndoorLayer extends JeoVectorLayer { MeshLayer mesh = t.layers.getMeshLayer(level * 3); if (mesh.area == null) { int color = JeoUtils.color(rule.color(f, CartoCSS.POLYGON_FILL, RGB.red)); - if (level > -2 && !activeLevels[level + 1]) + if (level > -2 && !active) color = Color.fade(color, 0.1f); mesh.area = new Area(color); @@ -96,20 +98,22 @@ public class OSMIndoorLayer extends JeoVectorLayer { addPolygon(t, g, mesh, ll); - Object o = f.get("name"); - if (o instanceof String) { - float x = 0; - float y = 0; - int n = mGeom.index[0]; - for (int i = 0; i < n;) { - x += mGeom.points[i++]; - y += mGeom.points[i++]; + if (active) { + Object o = f.get("name"); + if (o instanceof String) { + float x = 0; + float y = 0; + int n = mGeom.index[0]; + for (int i = 0; i < n;) { + x += mGeom.points[i++]; + y += mGeom.points[i++]; + } + + TextItem ti = TextItem.pool.get(); + ti.set(x / (n / 2) / 8, y / (n / 2) / 8, (String) o, mText); + + mTextLayer.addText(ti); } - - TextItem ti = TextItem.pool.get(); - ti.set(x / (n / 2) / 8, y / (n / 2) / 8, (String) o, mText); - - mTextLayer.addText(ti); } }