Subversion Repositories mkgmap

Rev

Rev 4735 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * Copyright (C) 2006 Steve Ratcliffe
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License 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.
 *
 *
 * Author: Steve Ratcliffe
 * Create date: 17-Dec-2006
 */

package uk.me.parabola.mkgmap.reader.osm;

import java.awt.Polygon;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.filters.ShapeMergeFilter;

/**
 * Represent a OSM way in the 0.5 api.  A way consists of an ordered list of
 * nodes.
 *
 * @author Steve Ratcliffe
 */

public class Way extends Element {
        private static final Logger log = Logger.getLogger(Way.class);
        private final List<Coord> points;
        private long fullArea = Long.MAX_VALUE; // meaning unset
        private MultiPolygonRelation mpRel;

        // This will be set if a way is read from an OSM file and the first node is the same node as the last
        // one in the way. This can be set to true even if there are missing nodes and so the nodes that we
        // have do not form a closed loop.
        // Note: this is not always set
        private boolean closedInOSM;

        // This is set to false if, we know that there are nodes missing from this way.
        // If you set this to false, then you *must* also set closed to the correct value.
        private boolean complete  = true;
       
        private boolean isViaWay;

        public Way(long id) {
                points = new ArrayList<>(5);
                setId(id);
        }

        public Way(long id, List<Coord> points) {
                this.points = new ArrayList<>(points);
                setId(id);
        }

        @Override
        public Way copy() {
                Way dup = new Way(getId(), points);
                dup.copyIds(this);
                dup.copyTags(this);
                dup.closedInOSM = this.closedInOSM;
                dup.complete = this.complete;
                dup.isViaWay = this.isViaWay;
                dup.fullArea = this.getFullArea();
                dup.mpRel = this.mpRel;
                return dup;
        }

        /**
         * Get the points that make up the way.  We attempt to re-order the segments
         * and return a list of points that traces the route of the way.
         *
         * @return A simple list of points that form a line.
         */

        public List<Coord> getPoints() {
                return points;
        }

        /**
         * @return first point or null if points is empty
         */

        public Coord getFirstPoint() {
                return points.isEmpty() ? null:points.get(0);
        }

        /**
         * @return last point or null if points is empty
         */

        public Coord getLastPoint() {
                return points.isEmpty() ? null:points.get(points.size() - 1);
        }

        public void addPoint(Coord co) {
                points.add(co);
        }

        public void addPointIfNotEqualToLastPoint(Coord co) {
                if(points.isEmpty() || !co.highPrecEquals(getLastPoint()))
                        points.add(co);
        }

        public void reverse() {
                Collections.reverse(points);
        }

        /**
         * Returns true if the way is really closed in OSM.
         *
         * Will return true even if the way is incomplete in the tile that we are reading, but the way is
         * really closed in OSM.
         *
         * @return True if the way is really closed.
         */

        public boolean isClosed() {
                if (!isComplete())
                        return closedInOSM;

                return !points.isEmpty() && hasIdenticalEndPoints();
        }

        /**
         *
         * @return true if the way is really closed in OSM,
         * false if the way was created by mkgmap or read from polish
         * input file (*.mp).
         *
         */

        public boolean isClosedInOSM() {
                return closedInOSM;
        }

        /**
         *  
         * @return Returns true if the first point in the way is identical to the last.
         */

        public boolean hasIdenticalEndPoints() {
                return !points.isEmpty() && points.get(0) == points.get(points.size()-1);
        }

        /**
         *  
         * @return Returns true if the first point in the way is identical to the last.
         */

        public boolean hasEqualEndPoints() {
                return !points.isEmpty() && points.get(0).equals(points.get(points.size()-1));
        }

        public void setClosedInOSM(boolean closed) {
                this.closedInOSM = closed;
        }

        public boolean isComplete() {
                return complete;
        }

        /**
         * Set this to false if you know that the way does not have its complete set of nodes.
         *
         * If you do set this to false, then you must also call {@link #setClosed} to indicate if the way
         * is really closed or not.
         */

        public void setComplete(boolean complete) {
                this.complete = complete;
        }

        /**
         * A simple representation of this way.
         * @return A string with the name, start point and end point
         */

        public String toString() {
                StringBuilder sb = new StringBuilder(super.toString());
                if (getName() != null) {
                        sb.append(' ').append(getName());
                }
                if (points.isEmpty())
                        sb.append(" empty");
                else {
                        Coord coord = getFirstPoint();
                        if (hasEqualEndPoints()) {
                                sb.append(" starting and ending at ").append(coord);
                        }
                        else {
                                sb.append(" starting at ").append(coord).append(" and ending at ").append(getLastPoint());
                        }
                }
                sb.append(' ').append(toTagString());
                return sb.toString();
        }

        public int hashCode() {
                return (int) getId();
        }

        public boolean equals(Object o) {
                if (this == o) return true;
                if (o == null || getClass() != o.getClass()) return false;

                return getId() == ((Way) o).getId();
        }

        /**
         * calculate weighted centre of way, using high precision
         * @return
         */

        public Coord getCofG() {
                int numPoints = points.size();
                if(numPoints < 1)
                        return null;

                double lat = 0;
                double lon = 0;
                if (hasIdenticalEndPoints())
                        numPoints--;
                for (int i = 0; i < numPoints; i++){
                        Coord p = points.get(i);
                        lat += (double)p.getHighPrecLat()/numPoints;
                        lon += (double)p.getHighPrecLon()/numPoints;
                }
                return Coord.makeHighPrecCoord((int)Math.round(lat), (int)Math.round(lon));
        }

        @Override
        public String kind() {
                return "way";
        }

        // returns true if the way is a closed polygon with a clockwise
        // direction
        public static boolean clockwise(List<Coord> points) {

               
                if(points.size() < 3 || !points.get(0).equals(points.get(points.size() - 1)))
                        return false;
                if (!points.get(0).highPrecEquals(points.get(points.size() - 1))) {
                        log.error("Way.clockwise was called for way that is not closed in high precision");
                }
               
                long area = 0;
                Coord p1 = points.get(0);
                for(int i = 1; i < points.size(); ++i) {
                        Coord p2 = points.get(i);
                        area += ((long)p1.getHighPrecLon() * p2.getHighPrecLat() -
                                         (long)p2.getHighPrecLon() * p1.getHighPrecLat());
                        p1 = p2;
                }

                // this test looks to be inverted but gives the expected result!
                // empty linear areas are defined as clockwise
                return area <= 0;
        }

        // simplistic check to see if this way "contains" another - for
        // speed, all we do is check that all of the other way's points
        // are inside this way's polygon
        public boolean containsPointsOf(Way other) {
                Polygon thisPoly = new Polygon();
                for(Coord p : points)
                        thisPoly.addPoint(p.getHighPrecLon(), p.getHighPrecLat());
                for(Coord p : other.points)
                        if(!thisPoly.contains(p.getHighPrecLon(), p.getHighPrecLat()))
                                return false;
                return true;
        }

        public boolean isViaWay() {
                return isViaWay;
        }

        public void setViaWay(boolean isViaWay) {
                this.isViaWay = isViaWay;
        }

        /**
         * Allows to manipulate the area size which might be used to sort shapes when
         * option --order-by-decreasing-area is active.
         * @param fullArea
         */

        public void setFullArea(long fullArea) {
                this.fullArea = fullArea;
        }

        public long getFullArea() { // this is unadulterated size, positive if clockwise
                if (this.fullArea == Long.MAX_VALUE && points.size() >= 4 && getFirstPoint().highPrecEquals(getLastPoint())) {
                        this.fullArea = ShapeMergeFilter.calcAreaSizeTestVal(points);
                }
                return this.fullArea;
        }
       
        public double calcLengthInMetres() {
                double length = 0;
                if (points.size() > 1) {
                        Coord p0 = points.get(0);
                        for (int i = 1; i < points.size(); i++) {
                                Coord p1 = points.get(i);
                                length += p0.distance(p1);
                                p0 = p1;
                        }
                }
                return length;
        }

        /**
         * @return the mpRel, null if the way was not created from a multipolygon with inner rings
         */

        public MultiPolygonRelation getMpRel() {
                return mpRel;
        }

        /**
         * @param mpRel the mpRel to set
         */

        public void setMpRel(MultiPolygonRelation mpRel) {
                this.mpRel = mpRel;
        }
}