Subversion Repositories mkgmap

Rev

Rev 4897 | View as "text/plain" | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * 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.LinkedHashMap;
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;

        private static class MergeData {
                final Rectangle2D bounds;
                final BlockingQueue<Area> toMerge;
                final AtomicBoolean ready = new AtomicBoolean(false);
                Path2D.Double tmpLandPath = new Path2D.Double();
                Area landArea = new Area();
                private final String key;

                public MergeData(Rectangle2D bounds, String key) {
                        this.key = key;
                        this.bounds = bounds;
                        toMerge = new LinkedBlockingQueue<>();
                }

                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 static List<Way> convertToWays(Area a, String naturalTag) {
                List<List<Coord>> pointLists = Java2DConverter.areaToShapes(a);
                List<Way> ways = new ArrayList<>(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() || !mergeData.toMerge.isEmpty()) {
                        // 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> wayMap = new LinkedHashMap<>();
                        List<List<Coord>> landParts = Java2DConverter.areaToShapes(mergeData.landArea);
                        Relation rel = new GeneralRelation(FakeIdGenerator.makeFakeId());
                        for (List<Coord> landPoints : landParts) {
                                Way landWay = new Way(FakeIdGenerator.makeFakeId(), landPoints);
                                wayMap.put(landWay.getId(), landWay);
                                rel.addElement("inner", landWay);
                        }

                        Way seaWay = new Way(FakeIdGenerator.makeFakeId(), uk.me.parabola.imgfmt.app.Area.PLANET.toCoords());
                        seaWay.setClosedInOSM(true);
                        wayMap.put(seaWay.getId(), seaWay);
                        rel.addElement("outer", seaWay);

                        // process the tile as sea multipolygon to create simple polygons only
                        MultiPolygonRelation mpr = new MultiPolygonRelation(rel, wayMap,
                                        Java2DConverter.createBbox(new Area(mergeData.bounds)))
                        {
                                // do not calculate the area size => it is not required and adds
                                // a superfluous tag
                                @Override
                                protected boolean needsAreaSizeTag() {
                                        return false;
                                }
                               
                                @Override
                                protected boolean noPartitioning() {
                                        return true;
                                }

                        };
                        mpr.addTag("type", "multipolygon");
                        mpr.addTag("natural", "sea");
                        mpr.processElements();

                        for (Way w : wayMap.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))
                                                && "sea".equals(w.getTag("natural"))) {
                                        // ignore the land polygons - we already have them in our list
                                        w.deleteTag(MultiPolygonRelation.STYLE_FILTER_TAG);
                                        w.deleteTag(MultiPolygonRelation.TKM_MP_CREATED);
                                        ways.add(w);
                                }
                        }
                }

                try {
                        // forward the ways to the queue of the saver thread
                        saveQueue.put(new SimpleEntry<>(mergeData.getKey(), ways));
                } catch (InterruptedException exp) {
                        exp.printStackTrace();
                }

                // signal that this tile is finished
                signal.countDown();
        }
}