/* * Copyright (C) 2012-2014. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 or * version 2 as published by the Free Software Foundation. * * 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 * General Public License for more details. */ package uk.me.parabola.mkgmap.sea.optional; import java.awt.geom.Area; import java.awt.geom.Path2D; import java.awt.geom.Rectangle2D; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import uk.me.parabola.imgfmt.app.Coord; import uk.me.parabola.mkgmap.reader.osm.FakeIdGenerator; import uk.me.parabola.mkgmap.reader.osm.GeneralRelation; import uk.me.parabola.mkgmap.reader.osm.MultiPolygonRelation; import uk.me.parabola.mkgmap.reader.osm.Relation; import uk.me.parabola.mkgmap.reader.osm.Way; import uk.me.parabola.util.Java2DConverter; /** * Merges the polygons for one precompiled sea tile. * @author WanMil */ class PrecompSeaMerger implements Runnable { private final MergeData mergeData; private final CountDownLatch signal; private final BlockingQueue<Entry<String, List<Way>>> saveQueue; private ExecutorService service; static class MergeData { public final Rectangle2D bounds; public final BlockingQueue<Area> toMerge; public final AtomicBoolean ready = new AtomicBoolean(false); public Path2D.Double tmpLandPath = new Path2D.Double(); public Area landArea = new Area(); private final String key; public MergeData(Rectangle2D bounds, String key) { this.key = key; this.bounds = bounds; toMerge = new LinkedBlockingQueue<Area>(); } public String getKey() { return key; } } public PrecompSeaMerger(Rectangle2D bounds, String key, CountDownLatch signal, BlockingQueue<Entry<String, List<Way>>> saveQueue) { this.mergeData = new MergeData(bounds, key); this.signal = signal; this.saveQueue = saveQueue; } public MergeData getMergeData() { return mergeData; } public BlockingQueue<Area> getQueue() { return mergeData.toMerge; } public void signalInputComplete() { mergeData.ready.set(true); } public void setExecutorService(ExecutorService service) { this.service = service; } public Rectangle2D getTileBounds() { return mergeData.bounds; } private List<Way> convertToWays(Area a, String naturalTag) { List<List<Coord>> pointLists = Java2DConverter.areaToShapes(a); List<Way> ways = new ArrayList<Way>(pointLists.size()); for (List<Coord> points : pointLists) { Way w = new Way(FakeIdGenerator.makeFakeId(), points); w.addTag("natural", naturalTag); w.setClosedInOSM(true); ways.add(w); } return ways; } public void run() { Area merge = null; try { merge = mergeData.toMerge.poll(5, TimeUnit.MILLISECONDS); } catch (InterruptedException exp) { exp.printStackTrace(); } int merges = 0; while (merge != null) { Area landClipped = new Area(mergeData.bounds); landClipped.intersect(merge); mergeData.tmpLandPath.append(landClipped, false); merges++; if (merges % 500 == 0) { // store each 500 polygons into a temporary area // and merge them after that. That seems to be quicker // than adding lots of very small areas to a highly // scattered area Area tmpLandArea = new Area(mergeData.tmpLandPath); mergeData.landArea.add(tmpLandArea); mergeData.tmpLandPath.reset(); } if (merges % 500 == 0) { break; } merge = mergeData.toMerge.poll(); } if (mergeData.ready.get() == false || mergeData.toMerge.isEmpty() == false) { // repost the merge thread service.execute(this); return; } if (mergeData.landArea.isEmpty()) mergeData.landArea = new Area(mergeData.tmpLandPath); else mergeData.landArea.add(new Area(mergeData.tmpLandPath)); mergeData.tmpLandPath = null; // post processing // // convert the land area to a list of ways List<Way> ways = convertToWays(mergeData.landArea, "land"); if (ways.isEmpty()) { // no land in this tile => create a sea way only ways.addAll(convertToWays(new Area(mergeData.bounds), "sea")); } else { Map<Long, Way> landWays = new HashMap<Long, Way>(); List<List<Coord>> landParts = Java2DConverter .areaToShapes(mergeData.landArea); for (List<Coord> landPoints : landParts) { Way landWay = new Way(FakeIdGenerator.makeFakeId(), landPoints); landWays.put(landWay.getId(), landWay); } Way seaWay = new Way(FakeIdGenerator.makeFakeId()); seaWay.addPoint(new Coord(-90.0d, -180.0d)); seaWay.addPoint(new Coord(90.0d, -180.0d)); seaWay.addPoint(new Coord(90.0d, 180.0d)); seaWay.addPoint(new Coord(-90.0d, 180.0d)); seaWay.addPoint(seaWay.getPoints().get(0)); // close shape seaWay.setClosedInOSM(true); landWays.put(seaWay.getId(), seaWay); Relation rel = new GeneralRelation(FakeIdGenerator.makeFakeId()); for (Way w : landWays.values()) { rel.addElement((w == seaWay ? "outer" : "inner"), w); } // process the tile as sea multipolygon to create simple polygons only MultiPolygonRelation mpr = new MultiPolygonRelation(rel, landWays, Java2DConverter.createBbox(new Area(mergeData.bounds))) { // do not calculate the area size => it is not required and adds // a superfluous tag protected boolean isAreaSizeCalculated() { return false; } }; mpr.addTag("type", "multipolygon"); mpr.addTag("natural", "sea"); mpr.processElements(); for (Way w : landWays.values()) { // process the polygon ways only // the mp processing also creates line ways which must // be ignored here if (MultiPolygonRelation.STYLE_FILTER_POLYGON.equals(w .getTag(MultiPolygonRelation.STYLE_FILTER_TAG))) { String tag = w.getTag("natural"); if ("sea".equals(tag) == false) { // ignore the land polygons - we already have them in our list continue; } w.deleteTag(MultiPolygonRelation.STYLE_FILTER_TAG); w.deleteTag(MultiPolygonRelation.MP_CREATED_TAG); ways.add(w); } } } try { // forward the ways to the queue of the saver thread saveQueue.put(new SimpleEntry<String, List<Way>>( mergeData.getKey(), ways)); } catch (InterruptedException exp) { exp.printStackTrace(); } // signal that this tile is finished signal.countDown(); } }