/* * 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.makeShortestBitStream(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.makeShortestBitStream(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()); } /** * * @param countAllNodes : false: count only coord nodes, true: count number nodes * @return */ public int getNodeCount(boolean countAllNodes ) { int idx = 0; int count = 0; for (Coord co : points) { if (idx++ > 0 && (co.getId() > 0 || countAllNodes && co.isNumberNode())) count++; } return count; } public boolean hasHouseNumbers() { if (!isRoad()) return false; return roaddef.hasHouseNumbers(); } }