/* * Copyright 2012-2014 Hannes Janetzek * * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * * This program is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. */ package org.oscim.layers.tile.vector; import static org.oscim.layers.tile.MapTile.State.LOADING; import org.oscim.core.GeometryBuffer.GeometryType; import org.oscim.core.MapElement; import org.oscim.core.MercatorProjection; import org.oscim.core.Tag; import org.oscim.core.TagSet; import org.oscim.core.Tile; import org.oscim.layers.tile.MapTile; import org.oscim.layers.tile.TileLoader; import org.oscim.renderer.bucket.LineBucket; import org.oscim.renderer.bucket.LineTexBucket; import org.oscim.renderer.bucket.MeshBucket; import org.oscim.renderer.bucket.PolygonBucket; import org.oscim.renderer.bucket.RenderBuckets; import org.oscim.theme.IRenderTheme; import org.oscim.theme.RenderTheme; import org.oscim.theme.styles.AreaStyle; import org.oscim.theme.styles.CircleStyle; import org.oscim.theme.styles.ExtrusionStyle; import org.oscim.theme.styles.LineStyle; import org.oscim.theme.styles.RenderStyle; import org.oscim.theme.styles.SymbolStyle; import org.oscim.theme.styles.TextStyle; import org.oscim.tiling.ITileDataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class VectorTileLoader extends TileLoader implements RenderStyle.Callback { static final Logger log = LoggerFactory.getLogger(VectorTileLoader.class); protected static final double STROKE_INCREASE = Math.sqrt(2.5); protected static final byte LAYERS = 11; public static final byte STROKE_MIN_ZOOM = 12; public static final byte STROKE_MAX_ZOOM = 17; protected IRenderTheme renderTheme; /** current TileDataSource used by this MapTileLoader */ protected ITileDataSource mTileDataSource; /** currently processed MapElement */ protected MapElement mElement; /** current line bucket (will be used for outline bucket) */ protected LineBucket mCurLineBucket; /** Current bucket for adding elements */ protected int mCurBucket; /** Line-scale-factor depending on zoom and latitude */ protected float mLineScale = 1.0f; protected RenderBuckets mBuckets; private final VectorTileLayer mTileLayer; public VectorTileLoader(VectorTileLayer tileLayer) { super(tileLayer.getManager()); mTileLayer = tileLayer; } @Override public void dispose() { if (mTileDataSource != null) mTileDataSource.dispose(); } @Override public void cancel() { if (mTileDataSource != null) mTileDataSource.cancel(); } @Override public boolean loadTile(MapTile tile) { if (mTileDataSource == null) { log.error("no tile source is set"); return false; } renderTheme = mTileLayer.getTheme(); if (renderTheme == null) { log.error("no theme is set"); return false; } //mTileLayer.getLoaderHooks(); /* account for area changes with latitude */ double lat = MercatorProjection.toLatitude(tile.y); mLineScale = (float) Math.pow(STROKE_INCREASE, tile.zoomLevel - STROKE_MIN_ZOOM); if (mLineScale < 1) mLineScale = 1; /* scale line width relative to latitude + PI * thumb */ mLineScale *= 0.4f + 0.6f * ((float) Math.sin(Math.abs(lat) * (Math.PI / 180))); mBuckets = new RenderBuckets(); tile.data = mBuckets; try { /* query data source, which calls process() callback */ mTileDataSource.query(tile, this); } catch (NullPointerException e) { log.debug("NPE {} {}", tile, e.getMessage()); e.printStackTrace(); } catch (Exception e) { log.debug("{} {}", tile, e.getMessage()); e.printStackTrace(); return false; } return true; } @Override public void completed(QueryResult result) { boolean ok = (result == QueryResult.SUCCESS); mTileLayer.callHooksComplete(mTile, ok); /* finish buckets- tessellate and cleanup on worker-thread */ mBuckets.prepare(); clearState(); super.completed(result); } protected static int getValidLayer(int layer) { if (layer < 0) { return 0; } else if (layer >= LAYERS) { return LAYERS - 1; } else { return layer; } } public void setDataSource(ITileDataSource dataSource) { dispose(); mTileDataSource = dataSource; } static class TagReplacement { public TagReplacement(String key) { this.key = key; this.tag = new Tag(key, null); } String key; Tag tag; } /** * Override this method to change tags that should be passed * to {@link RenderTheme} matching. * E.g. to replace tags that should not be cached in Rendertheme */ protected TagSet filterTags(TagSet tagSet) { return tagSet; } @Override public void process(MapElement element) { if (isCanceled() || !mTile.state(LOADING)) return; if (mTileLayer.callProcessHooks(mTile, mBuckets, element)) return; TagSet tags = filterTags(element.tags); if (tags == null) return; mElement = element; /* get and apply render instructions */ if (element.type == GeometryType.POINT) { renderNode(renderTheme.matchElement(element.type, tags, mTile.zoomLevel)); } else { mCurBucket = getValidLayer(element.layer) * renderTheme.getLevels(); renderWay(renderTheme.matchElement(element.type, tags, mTile.zoomLevel)); } clearState(); } protected void renderWay(RenderStyle[] style) { if (style == null) return; for (int i = 0, n = style.length; i < n; i++) style[i].renderWay(this); } protected void renderNode(RenderStyle[] style) { if (style == null) return; for (int i = 0, n = style.length; i < n; i++) style[i].renderNode(this); } protected void clearState() { mCurLineBucket = null; mElement = null; } /*** RenderThemeCallback ***/ @Override public void renderWay(LineStyle line, int level) { int nLevel = mCurBucket + level; if (line.stipple == 0) { if (line.outline && mCurLineBucket == null) { log.debug("missing line for outline! " + mElement.tags + " lvl:" + level + " layer:" + mElement.layer); return; } LineBucket lb = mBuckets.getLineBucket(nLevel); if (lb.line == null) { lb.line = line; lb.scale = line.fixed ? 1 : mLineScale; lb.setExtents(-4, Tile.SIZE + 4); } if (line.outline) { lb.addOutline(mCurLineBucket); return; } lb.addLine(mElement); /* keep reference for outline layer(s) */ mCurLineBucket = lb; } else { LineTexBucket lb = mBuckets.getLineTexBucket(nLevel); if (lb.line == null) { lb.line = line; float w = line.width; if (!line.fixed) w *= mLineScale; lb.width = w; } lb.addLine(mElement); } } /* slower to load (requires tesselation) and uses * more memory but should be faster to render */ protected final static boolean USE_MESH_POLY = false; @Override public void renderArea(AreaStyle area, int level) { /* dont add faded out polygon layers */ if (mTile.zoomLevel < area.fadeScale) return; int nLevel = mCurBucket + level; if (USE_MESH_POLY) { MeshBucket mb = mBuckets.getMeshBucket(nLevel); mb.area = area; mb.addMesh(mElement); } else { PolygonBucket pb = mBuckets.getPolygonBucket(nLevel); pb.area = area; pb.addPolygon(mElement.points, mElement.index); } } @Override public void renderSymbol(SymbolStyle symbol) { mTileLayer.callThemeHooks(mTile, mBuckets, mElement, symbol, 0); } @Override public void renderExtrusion(ExtrusionStyle extrusion, int level) { mTileLayer.callThemeHooks(mTile, mBuckets, mElement, extrusion, level); } @Override public void renderCircle(CircleStyle circle, int level) { } @Override public void renderText(TextStyle text) { mTileLayer.callThemeHooks(mTile, mBuckets, mElement, text, 0); } }