Subversion Repositories mkgmap

Rev

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

/*
 * Copyright (C) 2007 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: Jan 5, 2008
 */

package uk.me.parabola.imgfmt.app.net;

import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import uk.me.parabola.imgfmt.MapFailedException;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.app.BitWriter;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
import uk.me.parabola.imgfmt.app.Label;
import uk.me.parabola.imgfmt.app.lbl.City;
import uk.me.parabola.imgfmt.app.lbl.Zip;
import uk.me.parabola.imgfmt.app.trergn.Polyline;
import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.general.CityInfo;
import uk.me.parabola.mkgmap.general.ZipCodeInfo;

/**
 * A road definition.  This ties together all segments of a single road
 * and provides street address information.
 *
 * This corresponds to an entry in NET1, which is linked with the
 * polylines making up this road in RGN. Links to RGN are written
 * via RoadIndex, while writing links from RGN to NET1 is delayed
 * via setOffsetWriter.
 *
 * If the map includes routing, the NET1 record also points to
 * a NOD2 record, written by writeNod2.
 *
 * Edges in the routing graph ("arcs") link to the corresponding
 * road via the RoadDef, storing the NET1 offset via TableA,
 * which also includes some road information.
 *
 * @author Elrond
 * @author Steve Ratcliffe
 * @author Robert Vollmert
 */


public class RoadDef {
        private static final Logger log = Logger.getLogger(RoadDef.class);

        public static final int NET_FLAG_NODINFO  = 0x40;
        public static final int NET_FLAG_ADDRINFO = 0x10;
        private static final int NET_FLAG_UNK1     = 0x04; // lock on road?
        private static final int NET_FLAG_ONEWAY   = 0x02;

        private static final int NOD2_FLAG_UNK        = 0x01;
//      private static final int NOD2_FLAG_EXTRA_DATA = 0x80; just documentation

        // first byte of Table A info in NOD 1
        private static final int TABA_FLAG_TOLL = 0x80;
//      private static final int TABA_MASK_CLASS = 0x70; just documentation
        private static final int TABA_FLAG_ONEWAY = 0x08;
//      private static final int TABA_MASK_SPEED = 0x07; just documentation

        private static final int TABAACCESS_FLAG_CARPOOL = 0x0008;
        private static final int TABAACCESS_FLAG_NOTHROUGHROUTE = 0x0080;
       
        // second byte: access flags, bits 0x08, 0x80 are set separately
        private static final int TABAACCESS_FLAG_NO_EMERGENCY = 0x8000;
        private static final int TABAACCESS_FLAG_NO_DELIVERY  = 0x4000;
        private static final int TABAACCESS_FLAG_NO_CAR     = 0x0001;
        private static final int TABAACCESS_FLAG_NO_BUS     = 0x0002;
        private static final int TABAACCESS_FLAG_NO_TAXI    = 0x0004;
        private static final int TABAACCESS_FLAG_NO_FOOT    = 0x0010;
        private static final int TABAACCESS_FLAG_NO_BIKE    = 0x0020;
        private static final int TABAACCESS_FLAG_NO_TRUCK   = 0x0040;
       
        // true if road should not be added to NOD
        private boolean skipAddToNOD;
       
        // the offset in Nod2 of our Nod2 record
        private int offsetNod2;

        // the offset in Net1 of our Net1 record
        private int offsetNet1;

        /*
         * Everything that's relevant for writing to NET1.
         */

        private int netFlags = NET_FLAG_UNK1;

        // the allowed vehicles in mkgmap internal format
        private byte mkgmapAccess;
       
        // The road length units may be affected by other flags in the header as
        // there is doubt as to the formula.
        private int roadLength;

        // There can be up to 4 labels for the same road.
        private static final int MAX_LABELS = 4;

        private final Label[] labels = new Label[MAX_LABELS];
        private int numlabels;

        private final SortedMap<Integer,List<RoadIndex>> roadIndexes = new TreeMap<>();

        private boolean paved = true;
        private boolean ferry;
        private boolean roundabout;
        private boolean linkRoad;
        private boolean synthesised;
        private boolean flareCheck;
        private Set<String> messageIssued;

        private final List<Offset> rgnOffsets = new ArrayList<>();
        // for the NOD2 bit stream  
        private BitSet nod2BitSet;

        /*
         * Everything that's relevant for writing out Nod 2.
         */

        // This is the node associated with the road.  I'm not certain about how
        // this works, but in NOD2 each road has a reference to only one node.
        // This is that node.
        private RouteNode node;

        // the first point in the road is a node (the above routing node)
        private boolean startsWithNode = true;
        // number of nodes in the road
        private int nnodes;

        // always appears to be set
        private int nod2Flags = NOD2_FLAG_UNK;

        // The data for Table A
        private int tabAInfo;
        private int tabAAccess;

        // for diagnostic purposes
        private final long id;
        private final String name;
        private List<Numbers> numbersList;
        private List<City> cityList;
        private List<Zip> zipList;
        private int nodeCount;

        public RoadDef(long id, String name) {
                this.id = id;
                this.name = name;
        }

        /**
         * A constructor that is used when reading a file and you know the NET1 offset. When writing
         * the offsetNet1 field is filled in during the writing process.
         * @param id Road id
         * @param net1offset The offset in the road defs section of the NET file.
         * @param name The main of the road.
         */

        public RoadDef(long id, int net1offset, String name) {
                this.id = id;
                this.offsetNet1 = net1offset;
                this.name = name;
        }

        // for diagnostic purposes
        public String toString() {
                // assumes id is an OSM id
                String browseURL = "http://www.openstreetmap.org/browse/way/" + id;
                //if(getName() != null)
                //      return "(" + getName() + ", " + browseURL + ")";
                //else
                        return "(" + browseURL + ")";
        }

        public String getName() {
                if (name != null)
                        return name;
                if (labels[0] != null)
                        return labels[0].toString();
                return null;
        }

        public long getId() {
                return id;
        }


        /**
         * This is for writing to NET1.
         * @param writer A writer that is positioned within NET1.
         */

        void writeNet1(ImgFileWriter writer, int numCities, int numZips) {
                if (numlabels == 0)
                        return;
                assert numlabels > 0;
                Zip zip = getZips().isEmpty() ? null : getZips().get(0);
                City city = getCities().isEmpty() ? null: getCities().get(0);
                offsetNet1 = writer.position();
                NumberPreparer numbers = null;
                if (numbersList != null) {
                        numbers = new NumberPreparer(numbersList, zip, city, numCities, numZips);
                        if (!numbers.prepare()){
                                numbers = null;
                                log.warn("Invalid housenumbers in",this.toString());
                        }
                }

                writeLabels(writer);
                if (numbers != null && numbers.getSwapped()) {
                        netFlags |= 0x20; // swapped default; left=even, right=odd
                }
                writer.put((byte) netFlags);
                writer.put3(roadLength);

                int maxlevel = writeLevelCount(writer);

                writeLevelDivs(writer, maxlevel);

                if((netFlags & NET_FLAG_ADDRINFO) != 0) {
                        nodeCount--;
                        if (nodeCount + 2 != nnodes){
                                log.error("internal error? The nodeCount doesn't match value calculated by RoadNetWork:",this);
                        }
                        writer.put((byte) (nodeCount & 0xff)); // lo bits of node count

                        int code = ((nodeCount >> 8) & 0x3); // top bits of node count
                        int len, flag;
                       
                        ByteArrayOutputStream zipBuf = null, cityBuf = null;
                        len = (numbers == null)  ? 0: numbers.zipWriter.getBuffer().size();
                        if (len > 0){
                                zipBuf = numbers.zipWriter.getBuffer();
                                flag = Utils.numberToPointerSize(len) - 1;
                        } else
                                flag = (zip == null) ? 3 : 2;
                        code |= flag << 2;
                       
                        len = (numbers == null)  ? 0: numbers.cityWriter.getBuffer().size();
                        if (len > 0){
                                cityBuf = numbers.cityWriter.getBuffer();
                                flag = Utils.numberToPointerSize(len) - 1;
                        } else
                                flag = (city == null) ? 3 : 2;
                        code |= flag << 4;
                       
                        len = (numbers == null) ? 0 : numbers.fetchBitStream().getLength();
                        if (len > 0){
                                flag = Utils.numberToPointerSize(len) - 1;
                        } else
                                flag = 3;
                        code |= flag << 6;
                       
                        writer.put((byte)code);
//                      System.out.printf("%d %d %d\n", (code >> 2 & 0x3), (code >> 4 & 0x3), (code >> 6 & 0x3));  
                       
                        if (zipBuf != null){
                                len = zipBuf.size();
                                writer.putN(Utils.numberToPointerSize(len), len);
                                writer.put(zipBuf.toByteArray());
                        } else {
                                if(zip != null) {
                                        char zipIndex = (char)zip.getIndex();
                                        writer.putN(Utils.numberToPointerSize(numZips), zipIndex);
                                }
                        }
                        if (cityBuf != null){
                                len = cityBuf.size();
                                writer.putN(Utils.numberToPointerSize(len), len);
                                writer.put(cityBuf.toByteArray());
                        } else {
                                if(city != null) {
                                        char cityIndex = (char)city.getIndex();
                                        writer.putN(Utils.numberToPointerSize(numCities), cityIndex);
                                }
                        }
                        if (numbers != null) {
                                BitWriter bw = numbers.fetchBitStream();
                                writer.putN(Utils.numberToPointerSize(bw.getLength()), bw.getLength());
                                writer.put(bw.getBytes(), 0, bw.getLength());
                        }
                }

                if (hasNodInfo()) {
                        // This is the offset of an entry in NOD2
                        int val = offsetNod2;
                        if (val < 0x7fff) {
                                writer.put((byte) 1);
                                writer.putChar((char) val);
                        } else {
                                writer.put((byte) 2);
                                writer.put3(val);
                        }
                }
        }

        private void writeLabels(ImgFileWriter writer) {
                for (int i = 0; i < numlabels; i++) {
                        Label l = labels[i];
                        int ptr = l.getOffset();
                        if (i == (numlabels-1))
                                ptr |= 0x800000;
                        writer.put3(ptr);
                }
        }

        public void putSortedRoadEntry(ImgFileWriter writer, Label label) {
                for(int i = 0; i < labels.length && labels[i] != null; ++i) {
                        if(labels[i].equals(label)) {
                                writer.put3((i << 22) | offsetNet1);
                                return;
                        }
                }
        }

        private int writeLevelCount(ImgFileWriter writer) {
                int maxlevel = getMaxZoomLevel();
                for (int i = 0; i <= maxlevel; i++) {
                        List<RoadIndex> l = roadIndexes.get(i);
                        int b = (l == null) ? 0 : l.size();
                        assert b < 0x80 : "too many polylines at level " + i;
                        if (i == maxlevel)
                                b |= 0x80;
                        writer.put((byte) b);
                }
                return maxlevel;
        }

        private void writeLevelDivs(ImgFileWriter writer, int maxlevel) {
                for (int i = 0; i <= maxlevel; i++) {
                        List<RoadIndex> l = roadIndexes.get(i);
                        if (l != null) {
                                for (RoadIndex ri : l)
                                        ri.write(writer);
                        }
                }
        }

        public void addLabel(Label l) {
                int i;
                for (i = 0; i < MAX_LABELS && labels[i] != null; ++i) {
                        if (l.equals(labels[i])) {
                                // label already present
                                return;
                        }
                }

                if (i < MAX_LABELS) {
                        labels[i] = l;
                        ++numlabels;
                }
                else
                        log.warn(this.toString() + " discarding extra label (already have " + MAX_LABELS + ")");
        }

        public Label[] getLabels() {
                return labels;
        }

        /**
         * Add a polyline to this road.
         *
         * References to these are written to NET. At a given zoom
         * level, we're writing these in the order we get them,
         * which must(!) be the order the segments have
         * in the road.
         */

        public void addPolylineRef(Polyline pl) {
                if(log.isDebugEnabled())
                        log.debug("adding polyline ref", this, pl.getSubdiv());
                int level = pl.getSubdiv().getZoom().getLevel();
                List<RoadIndex> l = roadIndexes.get(level);
                if (l == null) {
                        l = new ArrayList<>();
                        roadIndexes.put(level, l);
                }
                l.add(new RoadIndex(pl));

                if (level == 0) {
                        nodeCount += pl.getNodeCount(hasHouseNumbers());
                }
        }

        private int getMaxZoomLevel() {
                return roadIndexes.lastKey();
        }

        public boolean connectedTo(RoadDef other) {
                List<RoadIndex> l = roadIndexes.get(0);
                if(l == null)
                        return false;

                List<RoadIndex> ol = other.roadIndexes.get(0);
                if(ol == null)
                        return false;

                for(RoadIndex ri : l)
                        for(RoadIndex ori : ol)
                                if(ri.getLine().sharesNodeWith(ori.getLine()))
                                        return true;
                return false;
        }

        public boolean sameDiv(RoadDef other) {
                return getStartSubdivNumber() == other.getStartSubdivNumber();
        }

        public int getStartSubdivNumber() {
                Integer key = roadIndexes.firstKey();
                return roadIndexes.get(key).get(0).getLine().getSubdiv().getNumber();
        }

        /**
         * Set the road length (in meters).
         */

        public void setLength(double lenInMeter) {
                roadLength = NODHeader.metersToRaw(lenInMeter);
        }

        public boolean hasHouseNumbers() {
                return numbersList != null && !numbersList.isEmpty();
        }

        /*
         * Everything that's relevant for writing to RGN.
         */

        class Offset {
                final int position;
                final int flags;

                Offset(int position, int flags) {
                        this.position = position;
                        this.flags = flags;
                }

                int getPosition() {
                        return position;
                }

                int getFlags() {
                        return flags;
                }
        }


        /**
         * Add a target location in the RGN section where we should write the
         * offset of this road def when it is written to NET.
         *
         * @param position The offset in RGN.
         * @param flags The flags that should be set.
         */

        public void addOffsetTarget(int position, int flags) {
                rgnOffsets.add(new Offset(position, flags));
        }

        /**
         * Write into the RGN the offset in net1 of this road.
         * @param rgn A writer for the rgn file.
         */

        void writeRgnOffsets(ImgFileWriter rgn) {
                if (offsetNet1 >= 0x400000)
                        throw new MapFailedException("Overflow of the NET1. The tile ("
                                                        + log.threadTag()
                                                        + ") must be split so that there are fewer roads in it");

                for (Offset off : rgnOffsets) {
                        rgn.position(off.getPosition());
                        rgn.put3(offsetNet1 | off.getFlags());
                }
        }

        private boolean internalNodes;

        /**
         * Does the road have any nodes besides start and end?
         * These can be number nodes or routing nodes.
         * This affects whether we need to write extra bits in
         * the bitstream in RGN.
         */

        public boolean hasInternalNodes() {
                return internalNodes;
        }

        public void setInternalNodes(boolean n) {
                internalNodes = n;
        }

        /**
         * Set the routing node associated with this road.
         *
         * This implies that the road has an entry in NOD 2
         * which will be pointed at from NET 1.
         */

        public void setNode(RouteNode node) {
                if (skipAddToNOD)
                        return;
                netFlags |= NET_FLAG_NODINFO;
                this.node = node;
        }

        public RouteNode getNode(){
                return node;
        }
       
        private boolean hasNodInfo() {
                return (netFlags & NET_FLAG_NODINFO) != 0;
        }

        public void setStartsWithNode(boolean s) {
                startsWithNode = s;
        }

        public void setNumNodes(int n) {
                nnodes = n;
        }

        public void setNumbersList(List<Numbers> numbersList) {
                if (numbersList != null && !numbersList.isEmpty()) {
                        this.numbersList = numbersList;
                        netFlags |= NET_FLAG_ADDRINFO;
                }
        }
       
        public List<Numbers> getNumbersList() {
                return numbersList;
        }


        /**
         * Write this road's NOD2 entry.
         *
         * Stores the writing position to be able to link here
         * from NET 1 later.
         *
         * @param writer A writer positioned in NOD2.
         */

        public void writeNod2(ImgFileWriter writer) {
                if (!hasNodInfo())
                        return;
                if (skipAddToNOD){
                        // should not happen
                        log.error("internal error: writeNod2 called for roaddef with skipAddToNOD=true");
                        return;
                }

                log.debug("writing nod2");

                offsetNod2 = writer.position();

                writer.put((byte) nod2Flags);
                writer.put3(node.getOffsetNod1()); // offset in nod1

                // this is related to the number of nodes, but there
                // is more to it...
                // For now, shift by one if the first node is not a
                // routing node.
                // If the road has house numbers, we count also
                // the number nodes, and these get a 0 in the bit stream.
                int nbits = nnodes;
                if (!startsWithNode)
                        nbits++;
                writer.putChar((char) nbits);
                boolean[] bits = new boolean[nbits];
               
                if (hasHouseNumbers()){
                        int off = startsWithNode ? 0 :1;
                        for (int i = 0; i < bits.length; i++){
                                if (nod2BitSet.get(i))
                                        bits[i+off] = true;
                        }
                } else {
                        for (int i = 0; i < bits.length; i++)
                                bits[i] = true;
                        if (!startsWithNode)
                                bits[0] = false;
                }
                for (int i = 0; i < bits.length; i += 8) {
                        int b = 0;
            for (int j = 0; j < 8 && j < bits.length - i; j++)
                                if (bits[i+j])
                                        b |= 1 << j;
                        writer.put((byte) b);
                }
        }

        /*
         * Everything that's relevant for writing out Table A.
         *
         * Storing this info in the RoadDef means that each
         * arc gets the same version of the below info, which
         * makes sense for the moment considering polish format
         * doesn't provide for different speeds and restrictions
         * for segments of roads.
         */


        /**
         * Return the offset of this road's NET1 entry. Assumes
         * writeNet1() has been called.
         */

        public int getOffsetNet1() {
                return offsetNet1;
        }

        /**
         * Flag that a toll must be payed when using this road.
         */

        public void setToll() {
                tabAInfo |= TABA_FLAG_TOLL;
        }
       
        /**
         * Flag that the road has a carpool lane.<br>
         * Warning: This bit does not seem to work. Maybe it does not control
         * the carpool flag.
         */

        public void setCarpoolLane() {
                tabAAccess |= TABAACCESS_FLAG_CARPOOL;
        }

        /**
         * Sets the flag that routing is allowed only if the route starts or
         * end on this road.
         */

        public void setNoThroughRouting() {
                tabAAccess |= TABAACCESS_FLAG_NOTHROUGHROUTE;
        }

        /**
         * @return allowed vehicles in mkgmap format  
         */

        public byte getAccess() {
                return mkgmapAccess;
        }

        /**
         * Set allowed vehicles
         * @param mkgmapAccess bit mask in mkgmap format
         */

        public void setAccess(byte mkgmapAccess) {
                this.mkgmapAccess = mkgmapAccess;
                // translate internal format to that used in TableA
                //clear the corresponding bits
                tabAAccess &= ~(0xc077);
                if (mkgmapAccess == (byte) 0xff)
                        return; // all vehicles allowed

                if ((mkgmapAccess & AccessTagsAndBits.FOOT) == 0)
                        tabAAccess |= TABAACCESS_FLAG_NO_FOOT;
                if ((mkgmapAccess & AccessTagsAndBits.BIKE) == 0)
                        tabAAccess |=TABAACCESS_FLAG_NO_BIKE;
                if ((mkgmapAccess & AccessTagsAndBits.CAR) == 0)
                        tabAAccess |=TABAACCESS_FLAG_NO_CAR;
                if ((mkgmapAccess & AccessTagsAndBits.DELIVERY) == 0)
                        tabAAccess |=TABAACCESS_FLAG_NO_DELIVERY;
                if ((mkgmapAccess & AccessTagsAndBits.TRUCK) == 0)
                        tabAAccess |=TABAACCESS_FLAG_NO_TRUCK;
                if ((mkgmapAccess & AccessTagsAndBits.BUS) == 0)
                        tabAAccess |=TABAACCESS_FLAG_NO_BUS;
                if ((mkgmapAccess & AccessTagsAndBits.TAXI) == 0)
                        tabAAccess |=TABAACCESS_FLAG_NO_TAXI;
                if ((mkgmapAccess & AccessTagsAndBits.EMERGENCY) == 0)
                        tabAAccess |=TABAACCESS_FLAG_NO_EMERGENCY;
        }
       
        public int getTabAInfo() {
                return tabAInfo;
        }

        public int getTabAAccess() {
                return tabAAccess;
        }

        /*
         * These affect various parts.
         */


        private int roadClass = -1;


        // road class that goes in various places (really?)
        public void setRoadClass(int roadClass) {
                assert roadClass < 0x08;
               
                /* for RouteArcs to get as their "destination class" */
                this.roadClass = roadClass;

                /* for Table A */
                int shifted = (roadClass << 4) & 0xff;
                tabAInfo |= shifted;

                /* for NOD 2 */
                nod2Flags |= shifted;
        }

        public int getRoadClass() {
                assert roadClass >= 0 : "roadClass not set";
                return roadClass;
        }

        public void setSpeed(int speed) {
                assert speed < 0x08;

                /* for Table A */
                tabAInfo |= speed;

                /* for NOD 2 */
                nod2Flags |= (speed << 1);
        }

        public int getRoadSpeed() {
                return tabAInfo & 7;
        }

        public void setOneway() {
                tabAInfo |= TABA_FLAG_ONEWAY;
                netFlags |= NET_FLAG_ONEWAY;
        }

        public boolean isOneway() {
                return (netFlags & NET_FLAG_ONEWAY) != 0;
        }

        public void addCityIfNotPresent(City city) {
                if (city == null){
                        log.error("trying to add null value to city list in road",this);
                        return;
                }
                netFlags |= NET_FLAG_ADDRINFO;
                if (cityList == null){
                        cityList = new ArrayList<>(2);
                }
                if (cityList.contains(city) == false)
                        cityList.add(city);
        }

        public void addZipIfNotPresent(Zip zip) {
                if (zip == null){
                        log.error("trying to add null value to zip list in road",this);
                        return;
                }
                netFlags |= NET_FLAG_ADDRINFO;
                if (zipList == null){
                        zipList = new ArrayList<>(2);
                }
                if (zipList.contains(zip) == false)
                        zipList.add(zip);
        }

       
        public List<City> getCities(){
                if (cityList == null)
                        return Collections.emptyList();
                return cityList;
        }

        public List<Zip> getZips(){
                if (zipList == null)
                        return Collections.emptyList();
                return zipList;
        }
       
        public boolean paved() {
                return paved;
        }

        public void paved(boolean p) {
                paved = p;
        }

        public void ferry(boolean f) {
                ferry = f;
        }

        public boolean ferry() {
                return ferry;
        }

        public void setRoundabout(boolean r) {
                roundabout = r;
        }

        public boolean isRoundabout() {
                return roundabout;
        }

        public void setLinkRoad(boolean lr) {
                linkRoad = lr;
        }

        public boolean isLinkRoad() {
                return linkRoad;
        }

        public void setSynthesised(boolean s) {
                synthesised = s;
        }

        public boolean isSynthesised() {
                return synthesised;
        }

        public void doFlareCheck(boolean fc) {
                flareCheck = fc;
        }

        public boolean doFlareCheck() {
                return flareCheck;
        }

        public boolean messagePreviouslyIssued(String key) {
                if(messageIssued == null)
                        messageIssued = new HashSet<>();
                boolean previouslyIssued = messageIssued.contains(key);
                messageIssued.add(key);
                return previouslyIssued;
        }

        public void setNod2BitSet(BitSet bs) {
                if (skipAddToNOD)
                        return;
                nod2BitSet = bs;
        }

        public boolean skipAddToNOD() {
                return skipAddToNOD;
        }

        public void skipAddToNOD(boolean skip) {
                this.skipAddToNOD = skip;
        }

        public void resetImgData() {
                zipList = null;
                cityList = null;
                if (numbersList != null){
                        for (Numbers num : numbersList){
                                for (int side = 0; side < 2; side++){
                                        boolean left = side == 0;
                                        CityInfo ci = num.getCityInfo(left);
                                        if (ci != null)
                                                ci.setImgCity(null);
                                        ZipCodeInfo z = num.getZipCodeInfo(left);
                                        if (z != null)
                                                z.setImgZip(null);
                                }
                        }
                }
        }
       
}