Subversion Repositories mkgmap

Rev

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

/*
 * Copyright (C) 2006 - 2012.
 *
 * 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.imgfmt.app.trergn;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import uk.me.parabola.imgfmt.app.BitWriter;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
import uk.me.parabola.imgfmt.app.Label;
import uk.me.parabola.imgfmt.app.net.RoadDef;
import uk.me.parabola.log.Logger;

/**
 * Represents a multi-segment line.  Eg for a road. As with all map objects
 * it can only exist as part of a subdivision.
 *
 * Writing these out is particularly tricky as deltas between points are packed
 * into the smallest number of bits possible.
 *
 * I am not trying to make the smallest map, so it will not be totally optimum.
 *
 * @author Steve Ratcliffe
 */

public class Polyline extends MapObject {
        private static final Logger log = Logger.getLogger(Polyline.class);

        // flags in the label offset
        private static final int FLAG_NETINFO = 0x800000;
        private static final int FLAG_EXTRABIT = 0x400000;

        // flags in the type
        private static final int FLAG_DIR = 0x40;
        private static final int FLAG_2BYTE_LEN = 0x80;

        // Reference to NET section, if any
        private RoadDef roaddef;

        // If a road gets subdivided into several segments, this
        // says whether this line is the last segment. Need this
        // for writing extra bits.
        private boolean lastSegment = true;

        // Set if it is a one-way street for example.
        private boolean direction;

        // The actual points that make up the line.
        private final List<Coord> points = new ArrayList<Coord>();

        public Polyline(Subdivision div) {
                setSubdiv(div);
        }

        /**
         * Format and write the contents of the object to the given
         * file.
         *
         * @param file A reference to the file that should be written to.
         */

        public void write(ImgFileWriter file) {

                // Prepare for writing by doing all the required calculations.

                LinePreparer w;
                try {
                        // Prepare the information that we need.
                        w = new LinePreparer(this);
                }
                catch (AssertionError ae) {
                        log.error("Problem writing line (" + getClass() + ") of type 0x" + Integer.toHexString(getType()) + " containing " + points.size() + " points and starting at " + points.get(0).toOSMURL());
                        log.error("  Subdivision shift is " + getSubdiv().getShift() +
                                          " and its centre is at " + getSubdiv().getCenter().toOSMURL());
                        log.error("  " + ae.getMessage());
                        if(roaddef != null)
                                log.error("  Way is " + roaddef);
                        return;
                }

                int minPointsRequired = (this instanceof Polygon)? 3 : 2;
                BitWriter bw = w.makeBitStream(minPointsRequired);
                if(bw == null) {
                        log.error("Level " + getSubdiv().getZoom().getLevel() + " " + ((this instanceof Polygon)? "polygon" : "polyline") + " has less than " + minPointsRequired + " points, discarding");
                        return;
                }

                // The type of feature, also contains a couple of flags hidden inside.
                byte b1 = (byte) getType();
                if (direction)
                        b1 |= FLAG_DIR;  // Polylines only.

                int blen = bw.getLength() - 1; // allow for the sizes
                assert blen > 0 : "zero length bitstream";
                assert blen < 0x10000 : "bitstream too long " + blen;
                if (blen >= 0x100)
                        b1 |= FLAG_2BYTE_LEN;

                file.put(b1);

                // The label, contains a couple of flags within it.
                int loff = getLabel().getOffset();
                if (w.isExtraBit())
                        loff |= FLAG_EXTRABIT;

                // If this is a road, then we need to save the offset of the label
                // so that we can change it to the index in the net section
                if (roaddef != null) {
                        roaddef.addLabel(getLabel());
                        roaddef.addOffsetTarget(file.position(),
                                        FLAG_NETINFO | (loff & FLAG_EXTRABIT));
                        // also add ref label(s) if present
                        List<Label> refLabels = getRefLabels();
                        if(refLabels != null)
                                for(Label rl : refLabels)
                                        roaddef.addLabel(rl);
                }

                file.put3(loff);

                // The delta of the longitude from the subdivision centre point
                // note that this has already been calculated.
                file.putChar((char) getDeltaLong());
                file.putChar((char) getDeltaLat());
                if(log.isDebugEnabled())
                        log.debug("out center", getDeltaLat(), getDeltaLong());

                if (blen < 0x100)
                        file.put((byte) (blen & 0xff));
                else
                        file.putChar((char) (blen & 0xffff));

                file.put(bw.getBytes(), 0, blen+1);
        }

        /*
         * write the polyline to an OutputStream - only use for outputting
         * lines with extended (3 byte) types.
         *
         */

        public void write(OutputStream stream) throws IOException {
                assert hasExtendedType();
                int type = getType();
                int labelOff = getLabel().getOffset();
                byte[] extraBytes = getExtTypeExtraBytes();

                LinePreparer w;
                try {
                        // need to prepare line info before outputing lat/lon
                        w = new LinePreparer(this);
                }
                catch (AssertionError ae) {
                        log.error("Problem writing line (" + getClass() + ") of type 0x" + Integer.toHexString(getType()) + " containing " + points.size() + " points and starting at " + points.get(0).toOSMURL());
                        log.error("  Subdivision shift is " + getSubdiv().getShift() +
                                          " and its centre is at " + getSubdiv().getCenter().toOSMURL());
                        log.error("  " + ae.getMessage());
                        if(roaddef != null)
                                log.error("  Way is " + roaddef);
                        return;
                }
                int minPointsRequired = (this instanceof Polygon)? 3 : 2;
                BitWriter bw = w.makeBitStream(minPointsRequired);
                if(bw == null) {
                        log.error("Level " + getSubdiv().getZoom().getLevel() + " " + ((this instanceof Polygon)? "polygon" : "polyline") + " has less than " + minPointsRequired + " points, discarding");
                        return;
                }
                int blen = bw.getLength();
                assert blen > 1 : "zero length bitstream";
                assert blen < 0x10000 : "bitstream too long " + blen;

                if(labelOff != 0)
                        type |= 0x20;           // has label
                if(extraBytes != null)
                        type |= 0x80;           // has extra bytes
                stream.write(type >> 8);
                stream.write(type);

                int deltaLong = getDeltaLong();
                int deltaLat = getDeltaLat();
                stream.write(deltaLong);
                stream.write(deltaLong >> 8);
                stream.write(deltaLat);
                stream.write(deltaLat >> 8);

                if (blen >= 0x7f) {
                        stream.write((blen << 2) | 2);
                        stream.write((blen << 2) >> 8);
                }
                else {
                        stream.write((blen << 1) | 1);
                }

                stream.write(bw.getBytes(), 0, blen);

                if(labelOff != 0) {
                        stream.write(labelOff);
                        stream.write(labelOff >> 8);
                        stream.write(labelOff >> 16);
                }

                if(extraBytes != null)
                        stream.write(extraBytes);
        }

        public void addCoord(Coord co) {
                points.add(co);
        }
       
        public void addCoords(List<Coord> coords) {
                points.addAll(coords);
        }

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

        public void setDirection(boolean direction) {
                this.direction = direction;
        }

        public boolean isRoad() {
                return roaddef != null;
        }

        public boolean roadHasInternalNodes() {
                return roaddef.hasInternalNodes();
        }

        public void setLastSegment(boolean last) {
                lastSegment = last;
        }

        public boolean isLastSegment() {
                return lastSegment;
        }

        public void setRoadDef(RoadDef rd) {
                this.roaddef = rd;
        }

        public int getOffsetNet1() {
                if (!isRoad())
                        return 0;
                return roaddef.getOffsetNet1();
        }

        public boolean sharesNodeWith(Polyline other) {
                for (Coord p1 : points) {
                        if (p1.getId() != 0) {
                                // point is a node, see if the other line contain the
                                // same node
                                for (Coord p2 : other.points)
                                        if (p1.getId() == p2.getId())
                                                return true;
                        }
                }

                return false;
        }

        public int getLat() {
                return getSubdiv().getLatitude() + (getDeltaLat() << getSubdiv().getShift());
        }

        public int getLong() {
                return getSubdiv().getLongitude() + (getDeltaLong() << getSubdiv().getShift());
        }

        public int getNodeCount() {
                int idx = 0;
                int count = 0;
                for (Coord co : points) {
                        if (idx++ > 0 && co.getId() > 0)
                                count++;
                }
                return count;
        }
}