content: package btools.mapaccess;
  
  import btools.codec.DataBuffers;
  import btools.codec.NoisyDiffCoder;
  import btools.codec.StatCoderContext;
  import btools.codec.TagValueCoder;
  import btools.codec.TagValueValidator;
  import btools.codec.TagValueWrapper;
  import btools.codec.WaypointMatcher;
  import btools.util.ByteDataWriter;
  
  /**
   * DirectWeaver does the same decoding as MicroCache2, but decodes directly
   * into the instance-graph, not into the intermediate nodes-cache
   */
  public final class DirectWeaver extends ByteDataWriter {
    private long id64Base;
  
    public DirectWeaver(StatCoderContext bc, DataBuffers dataBuffers, int lonIdx, int latIdx, int divisor, TagValueValidator wayValidator, WaypointMatcher waypointMatcher, OsmNodesMap hollowNodes) {
      super(null);
      int cellsize = 1000000 / divisor;
      id64Base = ((long) (lonIdx * cellsize)) << 32 | (latIdx * cellsize);
  
      TagValueCoder wayTagCoder = new TagValueCoder(bc, dataBuffers, wayValidator);
      TagValueCoder nodeTagCoder = new TagValueCoder(bc, dataBuffers, null);
      NoisyDiffCoder nodeIdxDiff = new NoisyDiffCoder(bc);
      NoisyDiffCoder nodeEleDiff = new NoisyDiffCoder(bc);
      NoisyDiffCoder extLonDiff = new NoisyDiffCoder(bc);
      NoisyDiffCoder extLatDiff = new NoisyDiffCoder(bc);
      NoisyDiffCoder transEleDiff = new NoisyDiffCoder(bc);
  
      int size = bc.decodeNoisyNumber(5);
  
      int[] faid = size > dataBuffers.ibuf2.length ? new int[size] : dataBuffers.ibuf2;
  
      bc.decodeSortedArray(faid, 0, size, 29, 0);
  
      OsmNode[] nodes = new OsmNode[size];
      for (int n = 0; n < size; n++) {
        long id = expandId(faid[n]);
        int ilon = (int) (id >> 32);
        int ilat = (int) (id & 0xffffffff);
        OsmNode node = hollowNodes.get(ilon, ilat);
        if (node == null) {
          node = new OsmNode(ilon, ilat);
        } else {
          node.visitID = 1;
          hollowNodes.remove(node);
        }
        nodes[n] = node;
      }
  
      int netdatasize = bc.decodeNoisyNumber(10); // (not needed for direct weaving)
      ab = dataBuffers.bbuf1;
      aboffset = 0;
  
      int selev = 0;
      for (int n = 0; n < size; n++) { // loop over nodes
        OsmNode node = nodes[n];
        int ilon = node.ilon;
        int ilat = node.ilat;
  
        // future escapes (turn restrictions?)
        short trExceptions = 0;
        for (; ; ) {
          int featureId = bc.decodeVarBits();
          if (featureId == 0) break;
          int bitsize = bc.decodeNoisyNumber(5);
  
          if (featureId == 2) { // exceptions to turn-restriction
            trExceptions = (short) bc.decodeBounded(1023);
          } else if (featureId == 1) { // turn-restriction
            TurnRestriction tr = new TurnRestriction();
            tr.exceptions = trExceptions;
            trExceptions = 0;
            tr.isPositive = bc.decodeBit();
            tr.fromLon = ilon + bc.decodeNoisyDiff(10);
            tr.fromLat = ilat + bc.decodeNoisyDiff(10);
            tr.toLon = ilon + bc.decodeNoisyDiff(10);
            tr.toLat = ilat + bc.decodeNoisyDiff(10);
            node.addTurnRestriction(tr);
          } else {
            for (int i = 0; i < bitsize; i++) bc.decodeBit(); // unknown feature, just skip
          }
        }
  
        selev += nodeEleDiff.decodeSignedValue();
        node.selev = (short) selev;
        TagValueWrapper nodeTags = nodeTagCoder.decodeTagValueSet();
        node.nodeDescription = nodeTags == null ? null : nodeTags.data; // TODO: unified?
  
        int links = bc.decodeNoisyNumber(1);
        for (int li = 0; li < links; li++) {
          int nodeIdx = n + nodeIdxDiff.decodeSignedValue();
  
          int dlon_remaining;
          int dlat_remaining;
  
          boolean isReverse = false;
          if (nodeIdx != n) { // internal (forward-) link
            dlon_remaining = nodes[nodeIdx].ilon - ilon;
            dlat_remaining = nodes[nodeIdx].ilat - ilat;
          } else {
            isReverse = bc.decodeBit();
            dlon_remaining = extLonDiff.decodeSignedValue();
            dlat_remaining = extLatDiff.decodeSignedValue();
          }
  
          TagValueWrapper wayTags = wayTagCoder.decodeTagValueSet();
  
          int linklon = ilon + dlon_remaining;
          int linklat = ilat + dlat_remaining;
          aboffset = 0;
          if (!isReverse) { // write geometry for forward links only
            WaypointMatcher matcher = wayTags == null || wayTags.accessType < 2 ? null : waypointMatcher;
            int ilontarget = ilon + dlon_remaining;
            int ilattarget = ilat + dlat_remaining;
            if (matcher != null) {
              boolean useAsStartWay = wayTags==null || wayValidator.checkStartWay(wayTags.data);
              if (!matcher.start(ilon, ilat, ilontarget, ilattarget, useAsStartWay)) {
                matcher = null;
              }
            }
  
            int transcount = bc.decodeVarBits();
            int count = transcount + 1;
            for (int i = 0; i < transcount; i++) {
              int dlon = bc.decodePredictedValue(dlon_remaining / count);
              int dlat = bc.decodePredictedValue(dlat_remaining / count);
              dlon_remaining -= dlon;
              dlat_remaining -= dlat;
              count--;
              int elediff = transEleDiff.decodeSignedValue();
              if (wayTags != null) {
                writeVarLengthSigned(dlon);
                writeVarLengthSigned(dlat);
                writeVarLengthSigned(elediff);
              }
  
              if (matcher != null)
                matcher.transferNode(ilontarget - dlon_remaining, ilattarget - dlat_remaining);
            }
            if (matcher != null) matcher.end();
          }
  
          if (wayTags != null) {
            byte[] geometry = null;
            if (aboffset > 0) {
              geometry = new byte[aboffset];
              System.arraycopy(ab, 0, geometry, 0, aboffset);
            }
  
            if (nodeIdx != n) { // valid internal (forward-) link
              OsmNode node2 = nodes[nodeIdx];
              OsmLink link = node.isLinkUnused() ? node : (node2.isLinkUnused() ? node2 : null);
              if (link == null) {
                link = new OsmLink();
              }
              link.descriptionBitmap = wayTags.data;
              link.geometry = geometry;
              node.addLink(link, isReverse, node2);
            } else { // weave external link
              node.addLink(linklon, linklat, wayTags.data, geometry, hollowNodes, isReverse);
              node.visitID = 1;
            }
          }
        } // ... loop over links
      } // ... loop over nodes
  
      hollowNodes.cleanupAndCount(nodes);
    }
  
    private static final long[] id32_00 = new long[1024];
    private static final long[] id32_10 = new long[1024];
    private static final long[] id32_20 = new long[1024];
  
    static {
      for (int i = 0; i < 1024; i++) {
        id32_00[i] = _expandId(i);
        id32_10[i] = _expandId(i << 10);
        id32_20[i] = _expandId(i << 20);
      }
    }
  
    private static long _expandId(int id32) {
      int dlon = 0;
      int dlat = 0;
  
      for (int bm = 1; bm < 0x8000; bm <<= 1) {
        if ((id32 & 1) != 0) dlon |= bm;
        if ((id32 & 2) != 0) dlat |= bm;
        id32 >>= 2;
      }
      return ((long) dlon) << 32 | dlat;
    }
  
    public long expandId(int id32) {
      return id64Base + id32_00[id32 & 1023] + id32_10[(id32 >> 10) & 1023] + id32_20[(id32 >> 20) & 1023];
    }
  }
  