// https://github.com/mbostock/shapefile Version 0.6.2. Copyright 2017 Mike Bostock.

var array_cancel = function() {
    this._array = null;
    return Promise.resolve();
  };
   
  var array_read = function() {
    var array = this._array;
    this._array = null;
    return Promise.resolve(array ? {done: false, value: array} : {done: true, value: undefined});
  };
  
  function array(array) {
    return new ArraySource(array instanceof Uint8Array ? array : new Uint8Array(array));
  }
  
  function ArraySource(array) {
    this._array = array;
  }
  
  ArraySource.prototype.read = array_read;
  ArraySource.prototype.cancel = array_cancel;
  
  var fetchPath = function(url) {
    return fetch(url).then(function(response) {
      return response.body && response.body.getReader
          ? response.body.getReader()
          : response.arrayBuffer().then(array);
    });
  };
  
  var requestPath = function(url) {
    return new Promise(function(resolve, reject) {
      var request = new XMLHttpRequest;
      request.responseType = "arraybuffer";
      request.onload = function() { resolve(array(request.response)); };
      request.onerror = reject;
      request.ontimeout = reject;
      request.open("GET", url, true);
      request.send();
    });
  };
  
  function path(path) {
    return (typeof fetch === "function" ? fetchPath : requestPath)(path);
  }
  
  function stream(source) {
    return typeof source.read === "function" ? source : source.getReader();
  }
  
  var empty = new Uint8Array(0);
  
  var slice_cancel = function() {
    return this._source.cancel();
  };
  
  function concat(a, b) {
    if (!a.length) return b;
    if (!b.length) return a;
    var c = new Uint8Array(a.length + b.length);
    c.set(a);
    c.set(b, a.length);
    return c;
  }
  
  var slice_read = function() {
    var that = this, array = that._array.subarray(that._index);
    return that._source.read().then(function(result) {
      that._array = empty;
      that._index = 0;
      return result.done ? (array.length > 0
          ? {done: false, value: array}
          : {done: true, value: undefined})
          : {done: false, value: concat(array, result.value)};
    });
  };
  
  var slice_slice = function(length) {
    if ((length |= 0) < 0) throw new Error("invalid length");
    var that = this, index = this._array.length - this._index;
  
    // If the request fits within the remaining buffer, resolve it immediately.
    if (this._index + length <= this._array.length) {
      return Promise.resolve(this._array.subarray(this._index, this._index += length));
    }
  
    // Otherwise, read chunks repeatedly until the request is fulfilled.
    var array = new Uint8Array(length);
    array.set(this._array.subarray(this._index));
    return (function read() {
      return that._source.read().then(function(result) {
  
        // When done, it’s possible the request wasn’t fully fullfilled!
        // If so, the pre-allocated array is too big and needs slicing.
        if (result.done) {
          that._array = empty;
          that._index = 0;
          return index > 0 ? array.subarray(0, index) : null;
        }
  
        // If this chunk fulfills the request, return the resulting array.
        if (index + result.value.length >= length) {
          that._array = result.value;
          that._index = length - index;
          array.set(result.value.subarray(0, length - index), index);
          return array;
        }
  
        // Otherwise copy this chunk into the array, then read the next chunk.
        array.set(result.value, index);
        index += result.value.length;
        return read();
      });
    })();
  };
  
  function slice(source) {
    return typeof source.slice === "function" ? source :
        new SliceSource(typeof source.read === "function" ? source
            : source.getReader());
  }
  
  function SliceSource(source) {
    this._source = source;
    this._array = empty;
    this._index = 0;
  }
  
  SliceSource.prototype.read = slice_read;
  SliceSource.prototype.slice = slice_slice;
  SliceSource.prototype.cancel = slice_cancel;
  
  var dbf_cancel = function() {
    return this._source.cancel();
  };
  
  var readBoolean = function(value) {
    return /^[nf]$/i.test(value) ? false
        : /^[yt]$/i.test(value) ? true
        : null;
  };
  
  var readDate = function(value) {
    return new Date(+value.substring(0, 4), value.substring(4, 6) - 1, +value.substring(6, 8));
  };
  
  var readNumber = function(value) {
    return !(value = value.trim()) || isNaN(value = +value) ? null : value;
  };
  
  var readString = function(value) {
    return value.trim() || null;
  };
  
  var types = {
    B: readNumber,
    C: readString,
    D: readDate,
    F: readNumber,
    L: readBoolean,
    M: readNumber,
    N: readNumber
  };
  
  var dbf_read = function() {
    var that = this, i = 1;
    return that._source.slice(that._recordLength).then(function(value) {
      return value && (value[0] !== 0x1a) ? {done: false, value: that._fields.reduce(function(p, f) {
        p[f.name] = types[f.type](that._decode(value.subarray(i, i += f.length)));
        return p;
      }, {})} : {done: true, value: undefined};
    });
  };
  
  var view = function(array) {
    return new DataView(array.buffer, array.byteOffset, array.byteLength);
  };
  
  var dbf = function(source, decoder) {
    source = slice(source);
    return source.slice(32).then(function(array) {
      var head = view(array);
      return source.slice(head.getUint16(8, true) - 32).then(function(array) {
        return new Dbf(source, decoder, head, view(array));
      });
    });
  };
  
  function Dbf(source, decoder, head, body) {
    this._source = source;
    this._decode = decoder.decode.bind(decoder);
    this._recordLength = head.getUint16(10, true);
    this._fields = [];
    for (var n = 0; body.getUint8(n) !== 0x0d; n += 32) {
      for (var j = 0; j < 11; ++j) if (body.getUint8(n + j) === 0) break;
      this._fields.push({
        name: this._decode(new Uint8Array(body.buffer, body.byteOffset + n, j)),
        type: String.fromCharCode(body.getUint8(n + 11)),
        length: body.getUint8(n + 16)
      });
    }
  }
  
  var prototype = Dbf.prototype;
  prototype.read = dbf_read;
  prototype.cancel = dbf_cancel;
  
  function cancel() {
    return this._source.cancel();
  }
  
  var readMultiPoint = function(record) {
    var i = 40, j, n = record.getInt32(36, true), coordinates = new Array(n);
    for (j = 0; j < n; ++j, i += 16) {
      coordinates[j] = [record.getFloat64(i, true), record.getFloat64(i + 8, true)];
      if (record.byteLength > i + 16) coordinates[j].push(record.getFloat64(i + 16, true))
    }
    return {type: "MultiPoint", coordinates: coordinates};
  };
  
  var readNull = function() {
    return null;
  };
  
  var readPoint = function(record) {
    //will read both point and pointZ
    const coordinates=[record.getFloat64(4, true), record.getFloat64(12, true)]
    if (record.byteLength > 20) coordinates.push(record.getFloat64(20, true))
    return {type: "Point", coordinates};
  };
  
  var readPolygon = function(record) {
    //will read both polygon and polygonz
    var i = 44, j, n = record.getInt32(36, true), m = record.getInt32(40, true), parts = new Array(n), points = new Array(m), polygons = [], holes = [];
    var hasZValues = record.byteLength >= 44 + 4 * n + 24 * m; // Check if there are enough bytes for z values
    
    for (j = 0; j < n; ++j, i += 4) parts[j] = record.getInt32(i, true);
    for (j = 0; j < m; ++j, i += 16) points[j] = [record.getFloat64(i, true), record.getFloat64(i + 8, true)];
  
    if (hasZValues) {
      for (j = 0; j < m; ++j, i += 8) points[j].push(record.getFloat64(i, true));
    }
    parts.forEach(function(i, j) {
      var ring = points.slice(i, parts[j + 1]);
      if (ringClockwise(ring)) polygons.push([ring]);
      else holes.push(ring);
    });
  
    holes.forEach(function(hole) {
      polygons.some(function(polygon) {
        if (ringContainsSome(polygon[0], hole)) {
          polygon.push(hole);
          return true;
        }
      }) || polygons.push([hole]);
    });
  
    return polygons.length === 1
        ? {type: "Polygon", coordinates: polygons[0]}
        : {type: "MultiPolygon", coordinates: polygons};
  };
  
  function ringClockwise(ring) {
    if ((n = ring.length) < 4) return false;
    var i = 0, n, area = ring[n - 1][1] * ring[0][0] - ring[n - 1][0] * ring[0][1];
    while (++i < n) area += ring[i - 1][1] * ring[i][0] - ring[i - 1][0] * ring[i][1];
    return area >= 0;
  }
  
  function ringContainsSome(ring, hole) {
    var i = -1, n = hole.length, c;
    while (++i < n) {
      if (c = ringContains(ring, hole[i])) {
        return c > 0;
      }
    }
    return false;
  }
  
  function ringContains(ring, point) {
    var x = point[0], y = point[1], contains = -1;
    for (var i = 0, n = ring.length, j = n - 1; i < n; j = i++) {
      var pi = ring[i], xi = pi[0], yi = pi[1],
          pj = ring[j], xj = pj[0], yj = pj[1];
      if (segmentContains(pi, pj, point)) {
        return 0;
      }
      if (((yi > y) !== (yj > y)) && ((x < (xj - xi) * (y - yi) / (yj - yi) + xi))) {
        contains = -contains;
      }
    }
    return contains;
  }
  
  function segmentContains(p0, p1, p2) {
    var x20 = p2[0] - p0[0], y20 = p2[1] - p0[1];
    if (x20 === 0 && y20 === 0) return true;
    var x10 = p1[0] - p0[0], y10 = p1[1] - p0[1];
    if (x10 === 0 && y10 === 0) return false;
    var t = (x20 * x10 + y20 * y10) / (x10 * x10 + y10 * y10);
    return t < 0 || t > 1 ? false : t === 0 || t === 1 ? true : t * x10 === x20 && t * y10 === y20;
  }
  
  var readPolyLine = function(record) {
    var i = 44, j, n = record.getInt32(36, true), m = record.getInt32(40, true), parts = new Array(n), points = new Array(m);
    for (j = 0; j < n; ++j, i += 4) parts[j] = record.getInt32(i, true);
    for (j = 0; j < m; ++j, i += 16) points[j] = [record.getFloat64(i, true), record.getFloat64(i + 8, true)];
    return n === 1
        ? {type: "LineString", coordinates: points}
        : {type: "MultiLineString", coordinates: parts.map(function(i, j) { return points.slice(i, parts[j + 1]); })};
  };
  
  var readPolyLineZ = function(record) {
    var i = 44, j, n = record.getInt32(36, true), m = record.getInt32(40, true), parts = new Array(n), points = new Array(m);
    for (j = 0; j < n; ++j, i += 4) parts[j] = record.getInt32(i, true);
    for (j = 0; j < m; ++j, i += 16) points[j] = [record.getFloat64(i, true), record.getFloat64(i + 8, true)];
    // Advance two doubles (z min, z max)
    i += 16;
    for (j = 0; j < m; ++j, i += 8) points[j].push(record.getFloat64(i, true));
    return n === 1
        ? {type: "LineStringZ", coordinates: points}
        : {type: "MultiLineStringZ", coordinates: parts.map(function(i, j) { return points.slice(i, parts[j + 1]); })};
  };
  
  var shp_read = function() {
    var that = this;
    return that._source.slice(8).then(function(array) {
      if (array == null) return {done: true, value: undefined};
      var header = view(array);
      return that._source.slice(header.getInt32(4, false) * 2).then(function(array) {
        var record = view(array);
        return {done: false, value: record.getInt32(0, true) ? that._type(record) : readNull()};
      });
    });
  };
  
  var types$1 = {
    0: readNull,
    1: readPoint,
    3: readPolyLine,
    5: readPolygon,
    8: readMultiPoint,
    11: readPoint,
    13: readPolyLineZ,
    15: readPolygon,
    18: readMultiPoint
  };
  
  var shp = function(source) {
    source = slice(source);
    return source.slice(100).then(function(array) {
      return new Shp(source, view(array));
    });
  };
  
  function Shp(source, header) {
    var type = header.getInt32(32, true);
    if (!(type in types$1)) throw new Error("unsupported shape type: " + type);
    this._source = source;
    this._type = types$1[type];
    this.bbox = [header.getFloat64(36, true), header.getFloat64(44, true), header.getFloat64(52, true), header.getFloat64(60, true)];
  }
  
  var prototype$2 = Shp.prototype;
  prototype$2.read = shp_read;
  prototype$2.cancel = cancel;
  
  function noop() {}
  
  var shapefile_cancel = function() {
    return Promise.all([
      this._dbf && this._dbf.cancel(),
      this._shp.cancel()
    ]).then(noop);
  };
  
  var shapefile_read = function() {
    var that = this;
    return Promise.all([
      that._dbf ? that._dbf.read() : {value: {}},
      that._shp.read()
    ]).then(function(results) {
      var dbf = results[0], shp = results[1];
      return shp.done ? shp : {
        done: false,
        value: {
          type: "Feature",
          properties: dbf.value,
          geometry: shp.value
        }
      };
    });
  };
  
  var shapefile = function(shpSource, dbfSource, decoder) {
    return Promise.all([
      shp(shpSource),
      dbfSource && dbf(dbfSource, decoder)
    ]).then(function(sources) {
      return new Shapefile(sources[0], sources[1]);
    });
  };
  
  function Shapefile(shp$$1, dbf$$1) {
    this._shp = shp$$1;
    this._dbf = dbf$$1;
    this.bbox = shp$$1.bbox;
  }
  
  var prototype$1 = Shapefile.prototype;
  prototype$1.read = shapefile_read;
  prototype$1.cancel = shapefile_cancel;
  
  function open(shp$$1, dbf$$1, options) {
    if (typeof dbf$$1 === "string") {
      if (!/\.dbf$/.test(dbf$$1)) dbf$$1 += ".dbf";
      dbf$$1 = path(dbf$$1, options);
    } else if (dbf$$1 instanceof ArrayBuffer || dbf$$1 instanceof Uint8Array) {
      dbf$$1 = array(dbf$$1);
    } else if (dbf$$1 != null) {
      dbf$$1 = stream(dbf$$1);
    }
    if (typeof shp$$1 === "string") {
      if (!/\.shp$/.test(shp$$1)) shp$$1 += ".shp";
      if (dbf$$1 === undefined) dbf$$1 = path(shp$$1.substring(0, shp$$1.length - 4) + ".dbf", options).catch(function() {});
      shp$$1 = path(shp$$1, options);
    } else if (shp$$1 instanceof ArrayBuffer || shp$$1 instanceof Uint8Array) {
      shp$$1 = array(shp$$1);
    } else {
      shp$$1 = stream(shp$$1);
    }
    return Promise.all([shp$$1, dbf$$1]).then(function(sources) {
      var shp$$1 = sources[0], dbf$$1 = sources[1], encoding = "windows-1252";
      if (options && options.encoding != null) encoding = options.encoding;
      return shapefile(shp$$1, dbf$$1, dbf$$1 && new TextDecoder(encoding));
    });
  }
  
  function openShp(source, options) {
    if (typeof source === "string") {
      if (!/\.shp$/.test(source)) source += ".shp";
      source = path(source, options);
    } else if (source instanceof ArrayBuffer || source instanceof Uint8Array) {
      source = array(source);
    } else {
      source = stream(source);
    }
    return Promise.resolve(source).then(shp);
  }
  
  function openDbf(source, options) {
    var encoding = "windows-1252";
    if (options && options.encoding != null) encoding = options.encoding;
    encoding = new TextDecoder(encoding);
    if (typeof source === "string") {
      if (!/\.dbf$/.test(source)) source += ".dbf";
      source = path(source, options);
    } else if (source instanceof ArrayBuffer || source instanceof Uint8Array) {
      source = array(source);
    } else {
      source = stream(source);
    }
    return Promise.resolve(source).then(function(source) {
      return dbf(source, encoding);
    });
  }
  
  function read(shp$$1, dbf$$1, options) {
    return open(shp$$1, dbf$$1, options).then(function(source) {
      var features = [], collection = {type: "FeatureCollection", features: features, bbox: source.bbox};
      return source.read().then(function read(result) {
        if (result.done) return collection;
        features.push(result.value);
        return source.read().then(read);
      });
    });
  }
  shapefile.open=open;
  shapefile.openShp=openShp;
  shapefile.openDbf=openDbf;
  shapefile.read=read;
  export {
  shapefile}