1114 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			1114 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
// import * as mars3d from "mars3d"
 | 
						|
 | 
						|
;(function (window) {
 | 
						|
  const BUF = 64
 | 
						|
  const tileSize = 256
 | 
						|
  const ArcGISPbfImageryProviderRequestTrailer = "/tile/{z}/{y}/{x}.pbf"
 | 
						|
 | 
						|
  class MarsPoint {
 | 
						|
    x
 | 
						|
    y
 | 
						|
    constructor(x, y) {
 | 
						|
      this.x = x
 | 
						|
      this.y = y
 | 
						|
    }
 | 
						|
 | 
						|
    clone() {
 | 
						|
      return new MarsPoint(this.x, this.y)
 | 
						|
    }
 | 
						|
 | 
						|
    add(p) {
 | 
						|
      return this.clone()._add(p)
 | 
						|
    }
 | 
						|
 | 
						|
    sub(p) {
 | 
						|
      return this.clone()._sub(p)
 | 
						|
    }
 | 
						|
 | 
						|
    multByPoint(p) {
 | 
						|
      return this.clone()._multByPoint(p)
 | 
						|
    }
 | 
						|
 | 
						|
    divByPoint(p) {
 | 
						|
      return this.clone()._divByPoint(p)
 | 
						|
    }
 | 
						|
 | 
						|
    mult(k) {
 | 
						|
      return this.clone()._mult(k)
 | 
						|
    }
 | 
						|
 | 
						|
    div(k) {
 | 
						|
      return this.clone()._div(k)
 | 
						|
    }
 | 
						|
 | 
						|
    rotate(a) {
 | 
						|
      return this.clone()._rotate(a)
 | 
						|
    }
 | 
						|
 | 
						|
    rotateAround(a, p) {
 | 
						|
      return this.clone()._rotateAround(a, p)
 | 
						|
    }
 | 
						|
 | 
						|
    matMult(m) {
 | 
						|
      return this.clone()._matMult(m)
 | 
						|
    }
 | 
						|
 | 
						|
    unit() {
 | 
						|
      return this.clone()._unit()
 | 
						|
    }
 | 
						|
 | 
						|
    perp() {
 | 
						|
      return this.clone()._perp()
 | 
						|
    }
 | 
						|
 | 
						|
    round() {
 | 
						|
      return this.clone()._round()
 | 
						|
    }
 | 
						|
 | 
						|
    mag() {
 | 
						|
      return Math.sqrt(this.x * this.x + this.y * this.y)
 | 
						|
    }
 | 
						|
 | 
						|
    equals(other) {
 | 
						|
      return this.x === other.x && this.y === other.y
 | 
						|
    }
 | 
						|
 | 
						|
    dist(p) {
 | 
						|
      return Math.sqrt(this.distSqr(p))
 | 
						|
    }
 | 
						|
 | 
						|
    distSqr(p) {
 | 
						|
      var dx = p.x - this.x,
 | 
						|
        dy = p.y - this.y
 | 
						|
      return dx * dx + dy * dy
 | 
						|
    }
 | 
						|
 | 
						|
    angle() {
 | 
						|
      return Math.atan2(this.y, this.x)
 | 
						|
    }
 | 
						|
 | 
						|
    angleTo(b) {
 | 
						|
      return Math.atan2(this.y - b.y, this.x - b.x)
 | 
						|
    }
 | 
						|
 | 
						|
    angleWith(b) {
 | 
						|
      return this.angleWithSep(b.x, b.y)
 | 
						|
    }
 | 
						|
 | 
						|
    angleWithSep(x, y) {
 | 
						|
      return Math.atan2(this.x * y - this.y * x, this.x * x + this.y * y)
 | 
						|
    }
 | 
						|
 | 
						|
    _matMult(m) {
 | 
						|
      var x = m[0] * this.x + m[1] * this.y,
 | 
						|
        y = m[2] * this.x + m[3] * this.y
 | 
						|
      this.x = x
 | 
						|
      this.y = y
 | 
						|
      return this
 | 
						|
    }
 | 
						|
 | 
						|
    _add(p) {
 | 
						|
      this.x += p.x
 | 
						|
      this.y += p.y
 | 
						|
      return this
 | 
						|
    }
 | 
						|
 | 
						|
    _sub(p) {
 | 
						|
      this.x -= p.x
 | 
						|
      this.y -= p.y
 | 
						|
      return this
 | 
						|
    }
 | 
						|
 | 
						|
    _mult(k) {
 | 
						|
      this.x *= k
 | 
						|
      this.y *= k
 | 
						|
      return this
 | 
						|
    }
 | 
						|
 | 
						|
    _div(k) {
 | 
						|
      this.x /= k
 | 
						|
      this.y /= k
 | 
						|
      return this
 | 
						|
    }
 | 
						|
 | 
						|
    _multByPoint(p) {
 | 
						|
      this.x *= p.x
 | 
						|
      this.y *= p.y
 | 
						|
      return this
 | 
						|
    }
 | 
						|
 | 
						|
    _divByPoint(p) {
 | 
						|
      this.x /= p.x
 | 
						|
      this.y /= p.y
 | 
						|
      return this
 | 
						|
    }
 | 
						|
 | 
						|
    _unit() {
 | 
						|
      this._div(this.mag())
 | 
						|
      return this
 | 
						|
    }
 | 
						|
 | 
						|
    _perp() {
 | 
						|
      var y = this.y
 | 
						|
      this.y = this.x
 | 
						|
      this.x = -y
 | 
						|
      return this
 | 
						|
    }
 | 
						|
 | 
						|
    _rotate(angle) {
 | 
						|
      var cos = Math.cos(angle),
 | 
						|
        sin = Math.sin(angle),
 | 
						|
        x = cos * this.x - sin * this.y,
 | 
						|
        y = sin * this.x + cos * this.y
 | 
						|
      this.x = x
 | 
						|
      this.y = y
 | 
						|
      return this
 | 
						|
    }
 | 
						|
 | 
						|
    _rotateAround(angle, p) {
 | 
						|
      var cos = Math.cos(angle),
 | 
						|
        sin = Math.sin(angle),
 | 
						|
        x = p.x + cos * (this.x - p.x) - sin * (this.y - p.y),
 | 
						|
        y = p.y + sin * (this.x - p.x) + cos * (this.y - p.y)
 | 
						|
      this.x = x
 | 
						|
      this.y = y
 | 
						|
      return this
 | 
						|
    }
 | 
						|
 | 
						|
    _round() {
 | 
						|
      this.x = Math.round(this.x)
 | 
						|
      this.y = Math.round(this.y)
 | 
						|
      return this
 | 
						|
    }
 | 
						|
 | 
						|
    static convert(a) {
 | 
						|
      if (a instanceof MarsPoint) {
 | 
						|
        return a
 | 
						|
      }
 | 
						|
      if (Array.isArray(a)) {
 | 
						|
        return new MarsPoint(a[0], a[1])
 | 
						|
      }
 | 
						|
      return a
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  class MarsSheet {
 | 
						|
    json
 | 
						|
    canvas
 | 
						|
    mapping
 | 
						|
    missingBox
 | 
						|
 | 
						|
    constructor(json, canvas) {
 | 
						|
      this.json = json
 | 
						|
      this.canvas = canvas
 | 
						|
      this.mapping = new Map()
 | 
						|
      this.missingBox = { x: 0, y: 0, w: 0, h: 0 }
 | 
						|
      const scale = window.devicePixelRatio
 | 
						|
      // 根据 key 分割精灵图
 | 
						|
      for (let i = 0; i < Object.keys(this.json).length; i++) {
 | 
						|
        const k = Object.keys(this.json)[i]
 | 
						|
        const v = Object.values(this.json)[i]
 | 
						|
        this.mapping.set(k, {
 | 
						|
          x: v.x,
 | 
						|
          y: v.y,
 | 
						|
          w: v.width * scale,
 | 
						|
          h: v.height * scale
 | 
						|
        })
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    get(name) {
 | 
						|
      let result = this.mapping.get(name)
 | 
						|
      if (!result) result = this.missingBox
 | 
						|
      return result
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  function linebreak(str, maxUnits) {
 | 
						|
    if (str.length <= maxUnits) return [str]
 | 
						|
    let endIndex = maxUnits - 1
 | 
						|
    let space_before = str.lastIndexOf(" ", endIndex)
 | 
						|
    let space_after = str.indexOf(" ", endIndex)
 | 
						|
    if (space_before == -1 && space_after == -1) {
 | 
						|
      return [str]
 | 
						|
    }
 | 
						|
    let first
 | 
						|
    let after
 | 
						|
    if (space_after == -1 || (space_before >= 0 && endIndex - space_before < space_after - endIndex)) {
 | 
						|
      first = str.substring(0, space_before)
 | 
						|
      after = str.substring(space_before + 1, str.length)
 | 
						|
    } else {
 | 
						|
      first = str.substring(0, space_after)
 | 
						|
      after = str.substring(space_after + 1, str.length)
 | 
						|
    }
 | 
						|
    return [first, ...linebreak(after, maxUnits)]
 | 
						|
  }
 | 
						|
 | 
						|
  class FontAttr {
 | 
						|
    family
 | 
						|
    size
 | 
						|
    weight
 | 
						|
    style
 | 
						|
    font
 | 
						|
 | 
						|
    constructor(options) {
 | 
						|
      if (options?.font) {
 | 
						|
        this.font = options.font
 | 
						|
      } else {
 | 
						|
        this.family = options?.fontFamily ?? "sans-serif"
 | 
						|
        this.size = options?.fontSize ?? 12
 | 
						|
        this.weight = options?.fontWeight
 | 
						|
        this.style = options?.fontStyle
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    get(z, f) {
 | 
						|
      if (this.font) {
 | 
						|
        if (typeof this.font === "function") {
 | 
						|
          return this.font(z, f)
 | 
						|
        } else {
 | 
						|
          return this.font
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
        let style = ""
 | 
						|
        if (this.style) {
 | 
						|
          if (typeof this.style === "function") {
 | 
						|
            style = this.style(z, f) + " "
 | 
						|
          } else {
 | 
						|
            style = this.style + " "
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        let weight = ""
 | 
						|
        if (this.weight) {
 | 
						|
          if (typeof this.weight === "function") {
 | 
						|
            weight = this.weight(z, f) + " "
 | 
						|
          } else {
 | 
						|
            weight = this.weight + " "
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        let size
 | 
						|
        if (typeof this.size === "function") {
 | 
						|
          size = this.size(z, f)
 | 
						|
        } else {
 | 
						|
          size = this.size
 | 
						|
        }
 | 
						|
 | 
						|
        let family
 | 
						|
        if (typeof this.family === "function") {
 | 
						|
          family = this.family(z, f)
 | 
						|
        } else {
 | 
						|
          family = this.family
 | 
						|
        }
 | 
						|
 | 
						|
        return `${style}${weight}${size}px ${family}`
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  class TextAttr {
 | 
						|
    label_props
 | 
						|
    textTransform
 | 
						|
 | 
						|
    constructor(options) {
 | 
						|
      this.label_props = options?.label_props ?? ["name"]
 | 
						|
      this.textTransform = options?.textTransform
 | 
						|
    }
 | 
						|
 | 
						|
    get(z, f) {
 | 
						|
      let retval
 | 
						|
 | 
						|
      let label_props
 | 
						|
      if (typeof this.label_props == "function") {
 | 
						|
        label_props = this.label_props(z, f)
 | 
						|
      } else {
 | 
						|
        label_props = this.label_props
 | 
						|
      }
 | 
						|
      for (let property of label_props) {
 | 
						|
        if (f.props.hasOwnProperty(property) && typeof f.props[property] === "string") {
 | 
						|
          retval = f.props[property]
 | 
						|
          break
 | 
						|
        }
 | 
						|
      }
 | 
						|
      let transform
 | 
						|
      if (typeof this.textTransform === "function") {
 | 
						|
        transform = this.textTransform(z, f)
 | 
						|
      } else {
 | 
						|
        transform = this.textTransform
 | 
						|
      }
 | 
						|
      if (retval && transform === "uppercase") retval = retval.toUpperCase()
 | 
						|
      else if (retval && transform === "lowercase") retval = retval.toLowerCase()
 | 
						|
      else if (retval && transform === "capitalize") {
 | 
						|
        const wordsArray = retval.toLowerCase().split(" ")
 | 
						|
        const capsArray = wordsArray.map((word) => {
 | 
						|
          return word[0].toUpperCase() + word.slice(1)
 | 
						|
        })
 | 
						|
        retval = capsArray.join(" ")
 | 
						|
      }
 | 
						|
      return retval
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  class StringAttr {
 | 
						|
    str
 | 
						|
    per_feature
 | 
						|
 | 
						|
    constructor(c, defaultValue) {
 | 
						|
      this.str = c ?? defaultValue
 | 
						|
      this.per_feature = typeof this.str == "function" && this.str.length == 2
 | 
						|
    }
 | 
						|
 | 
						|
    get(z, f) {
 | 
						|
      if (typeof this.str === "function") {
 | 
						|
        return this.str(z, f)
 | 
						|
      } else {
 | 
						|
        return this.str
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  class NumberAttr {
 | 
						|
    value
 | 
						|
    per_feature
 | 
						|
 | 
						|
    constructor(c, defaultValue = 1) {
 | 
						|
      this.value = c ?? defaultValue
 | 
						|
      this.per_feature = typeof this.value == "function" && this.value.length == 2
 | 
						|
    }
 | 
						|
 | 
						|
    get(z, f) {
 | 
						|
      if (typeof this.value == "function") {
 | 
						|
        return this.value(z, f)
 | 
						|
      } else {
 | 
						|
        return this.value
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // 文本符号化工具
 | 
						|
  class TextSymbolizer {
 | 
						|
    font
 | 
						|
    text
 | 
						|
    fillAttr
 | 
						|
    strokeAttr
 | 
						|
    width
 | 
						|
    lineHeight // in ems
 | 
						|
    letterSpacing // in px
 | 
						|
    maxLineCodeUnits
 | 
						|
    justify
 | 
						|
 | 
						|
    constructor(options) {
 | 
						|
      this.font = new FontAttr(options)
 | 
						|
      this.text = new TextAttr(options)
 | 
						|
 | 
						|
      this.fill = new StringAttr(options.fill, "black")
 | 
						|
      this.stroke = new StringAttr(options.stroke, "black")
 | 
						|
      this.width = new NumberAttr(options.width, 0)
 | 
						|
      this.lineHeight = new NumberAttr(options.lineHeight, 1)
 | 
						|
      this.letterSpacing = new NumberAttr(options.letterSpacing, 0)
 | 
						|
      this.maxLineCodeUnits = new NumberAttr(options.maxLineChars, 15)
 | 
						|
      this.justify = options.justify
 | 
						|
    }
 | 
						|
 | 
						|
    place(layout, geom, feature) {
 | 
						|
      let property = this.text.get(layout.zoom, feature)
 | 
						|
      if (!property) return undefined
 | 
						|
      let font = this.font.get(layout.zoom, feature)
 | 
						|
      layout.scratch.font = font
 | 
						|
 | 
						|
      let letterSpacing = this.letterSpacing.get(layout.zoom, feature)
 | 
						|
 | 
						|
      // line breaking
 | 
						|
      let lines = linebreak(property, this.maxLineCodeUnits.get(layout.zoom, feature))
 | 
						|
      let longestLine = ""
 | 
						|
      let longestLineLen = 0
 | 
						|
      for (let line of lines) {
 | 
						|
        if (line.length > longestLineLen) {
 | 
						|
          longestLineLen = line.length
 | 
						|
          longestLine = line
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      let metrics = layout.scratch.measureText(longestLine)
 | 
						|
      let width = metrics.width + letterSpacing * (longestLineLen - 1)
 | 
						|
 | 
						|
      let ascent = metrics.actualBoundingBoxAscent
 | 
						|
      let descent = metrics.actualBoundingBoxDescent
 | 
						|
      let lineHeight = (ascent + descent) * this.lineHeight.get(layout.zoom, feature)
 | 
						|
 | 
						|
      let a = new MarsPoint(geom[0][0].x, geom[0][0].y)
 | 
						|
      let bbox = {
 | 
						|
        minX: a.x,
 | 
						|
        minY: a.y - ascent,
 | 
						|
        maxX: a.x + width,
 | 
						|
        maxY: a.y + descent + (lines.length - 1) * lineHeight
 | 
						|
      }
 | 
						|
 | 
						|
      // inside draw, the origin is the anchor
 | 
						|
      // and the anchor is the typographic baseline of the first line
 | 
						|
      let draw = (ctx, extra) => {
 | 
						|
        ctx.globalAlpha = 1
 | 
						|
        ctx.font = font
 | 
						|
        ctx.fillStyle = this.fill.get(layout.zoom, feature)
 | 
						|
        let textStrokeWidth = this.width.get(layout.zoom, feature)
 | 
						|
 | 
						|
        let y = 0
 | 
						|
        for (let line of lines) {
 | 
						|
          let startX = 0
 | 
						|
          if (this.justify == protomaps.Justify.Center || (extra && extra.justify == protomaps.Justify.Center)) {
 | 
						|
            startX = (width - ctx.measureText(line).width) / 2
 | 
						|
          } else if (this.justify == protomaps.Justify.Right || (extra && extra.justify == protomaps.Justify.Right)) {
 | 
						|
            startX = width - ctx.measureText(line).width
 | 
						|
          }
 | 
						|
          if (textStrokeWidth) {
 | 
						|
            ctx.lineWidth = textStrokeWidth * 2 // centered stroke
 | 
						|
            ctx.strokeStyle = this.stroke.get(layout.zoom, feature)
 | 
						|
            if (letterSpacing > 0) {
 | 
						|
              let xPos = startX
 | 
						|
              for (let letter of line) {
 | 
						|
                ctx.strokeText(letter, xPos, y)
 | 
						|
                xPos += ctx.measureText(letter).width + letterSpacing
 | 
						|
              }
 | 
						|
            } else {
 | 
						|
              ctx.strokeText(line, startX, y)
 | 
						|
            }
 | 
						|
          }
 | 
						|
          if (letterSpacing > 0) {
 | 
						|
            let xPos = startX
 | 
						|
            for (let letter of line) {
 | 
						|
              ctx.fillText(letter, xPos, y)
 | 
						|
              xPos += ctx.measureText(letter).width + letterSpacing
 | 
						|
            }
 | 
						|
          } else {
 | 
						|
            ctx.fillText(line, startX, y)
 | 
						|
          }
 | 
						|
          y += lineHeight
 | 
						|
        }
 | 
						|
      }
 | 
						|
      return [{ anchor: a, bboxes: [bbox], draw: draw }]
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  class IconSymbolizer {
 | 
						|
    name
 | 
						|
    sheet
 | 
						|
    dpr
 | 
						|
    textSymbolizer
 | 
						|
 | 
						|
    constructor(options) {
 | 
						|
      this.name = options.name
 | 
						|
      this.sheet = options.sheet
 | 
						|
      this.dpr = window.devicePixelRatio
 | 
						|
      this.textSymbolizer = new TextSymbolizer(options)
 | 
						|
    }
 | 
						|
 | 
						|
    place(layout, geom, feature) {
 | 
						|
      let pt = geom[0]
 | 
						|
      let a = new MarsPoint(geom[0][0].x, geom[0][0].y)
 | 
						|
      let loc = this.sheet.get(this.name)
 | 
						|
      let width = loc.w / this.dpr
 | 
						|
      let height = loc.h / this.dpr
 | 
						|
 | 
						|
      const text = this.textSymbolizer.place(layout, geom, feature)
 | 
						|
 | 
						|
      let bbox = {
 | 
						|
        minX: a.x - width / 2,
 | 
						|
        minY: a.y - height / 2,
 | 
						|
        maxX: a.x + width / 2,
 | 
						|
        maxY: a.y + height / 2
 | 
						|
      }
 | 
						|
 | 
						|
      let draw = (ctx) => {
 | 
						|
        ctx.globalAlpha = 1
 | 
						|
        if (text) {
 | 
						|
          text[0].draw(ctx)
 | 
						|
        }
 | 
						|
 | 
						|
        ctx.drawImage(this.sheet.canvas, loc.x, loc.y, loc.w, loc.h, -loc.w / 2 / this.dpr, -loc.h / 2 / this.dpr, loc.w / 2, loc.h / 2)
 | 
						|
      }
 | 
						|
      return [{ anchor: a, bboxes: [bbox], draw: draw }]
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  function number(val, defaultValue) {
 | 
						|
    return typeof val === "number" ? val : defaultValue
 | 
						|
  }
 | 
						|
 | 
						|
  function filterFn(arr) {
 | 
						|
    // hack around "$type"
 | 
						|
    if (arr.includes("$type")) {
 | 
						|
      return (z) => true
 | 
						|
    } else if (arr[0] == "==") {
 | 
						|
      return (z, f) => f.props[arr[1]] === arr[2]
 | 
						|
    } else if (arr[0] == "!=") {
 | 
						|
      return (z, f) => f.props[arr[1]] !== arr[2]
 | 
						|
    } else if (arr[0] == "!") {
 | 
						|
      let sub = filterFn(arr[1])
 | 
						|
      return (z, f) => !sub(z, f)
 | 
						|
    } else if (arr[0] === "<") {
 | 
						|
      return (z, f) => number(f.props[arr[1]], Infinity) < arr[2]
 | 
						|
    } else if (arr[0] === "<=") {
 | 
						|
      return (z, f) => number(f.props[arr[1]], Infinity) <= arr[2]
 | 
						|
    } else if (arr[0] === ">") {
 | 
						|
      return (z, f) => number(f.props[arr[1]], -Infinity) > arr[2]
 | 
						|
    } else if (arr[0] === ">=") {
 | 
						|
      return (z, f) => number(f.props[arr[1]], -Infinity) >= arr[2]
 | 
						|
    } else if (arr[0] === "in") {
 | 
						|
      return (z, f) => arr.slice(2, arr.length).includes(f.props[arr[1]])
 | 
						|
    } else if (arr[0] === "!in") {
 | 
						|
      return (z, f) => !arr.slice(2, arr.length).includes(f.props[arr[1]])
 | 
						|
    } else if (arr[0] === "has") {
 | 
						|
      return (z, f) => f.props.hasOwnProperty(arr[1])
 | 
						|
    } else if (arr[0] === "!has") {
 | 
						|
      return (z, f) => !f.props.hasOwnProperty(arr[1])
 | 
						|
    } else if (arr[0] === "all") {
 | 
						|
      let parts = arr.slice(1, arr.length).map((e) => filterFn(e))
 | 
						|
      return (z, f) =>
 | 
						|
        parts.every((p) => {
 | 
						|
          return p(z, f)
 | 
						|
        })
 | 
						|
    } else if (arr[0] === "any") {
 | 
						|
      let parts = arr.slice(1, arr.length).map((e) => filterFn(e))
 | 
						|
      return (z, f) =>
 | 
						|
        parts.some((p) => {
 | 
						|
          return p(z, f)
 | 
						|
        })
 | 
						|
    } else {
 | 
						|
      console.log("Unimplemented filter: ", arr[0])
 | 
						|
      return (f) => false
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  function numberFn(obj) {
 | 
						|
    if (!obj.base) {
 | 
						|
      obj.base = 1
 | 
						|
    }
 | 
						|
    if (obj.base && obj.stops) {
 | 
						|
      return (z) => {
 | 
						|
        return protomaps.exp(obj.base, obj.stops)(z - 1)
 | 
						|
      }
 | 
						|
    } else if (obj[0] == "interpolate" && obj[1][0] == "exponential" && obj[2] == "zoom") {
 | 
						|
      let slice = obj.slice(3)
 | 
						|
      let stops = []
 | 
						|
      for (let i = 0; i < slice.length; i += 2) {
 | 
						|
        stops.push([slice[i], slice[i + 1]])
 | 
						|
      }
 | 
						|
      return (z) => {
 | 
						|
        return protomaps.exp(obj[1][1], stops)(z - 1)
 | 
						|
      }
 | 
						|
    } else if (obj[0] == "step" && obj[1][0] == "get") {
 | 
						|
      let slice = obj.slice(2)
 | 
						|
      let prop = obj[1][1]
 | 
						|
      return (z, f) => {
 | 
						|
        let val = f?.props[prop]
 | 
						|
        if (typeof val === "number") {
 | 
						|
          if (val < slice[1]) return slice[0]
 | 
						|
          for (let i = 1; i < slice.length; i += 2) {
 | 
						|
            if (val <= slice[i]) return slice[i + 1]
 | 
						|
          }
 | 
						|
        }
 | 
						|
        return slice[slice.length - 1]
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      console.log("Unimplemented numeric fn: ", obj)
 | 
						|
      return (z) => 1
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  function numberOrFn(obj, defaultValue = 0) {
 | 
						|
    if (!obj) return defaultValue
 | 
						|
    if (typeof obj == "number") {
 | 
						|
      return obj
 | 
						|
    }
 | 
						|
    // If feature f is defined, use numberFn, otherwise use defaultValue
 | 
						|
    return (z, f) => (f ? numberFn(obj)(z, f) : defaultValue)
 | 
						|
  }
 | 
						|
 | 
						|
  function widthFn(width_obj, gap_obj) {
 | 
						|
    let w = numberOrFn(width_obj, 1)
 | 
						|
    let g = numberOrFn(gap_obj)
 | 
						|
    return (z, f) => {
 | 
						|
      let tmp = typeof w == "number" ? w : w(z, f)
 | 
						|
      if (g) {
 | 
						|
        return tmp + (typeof g == "number" ? g : g(z, f))
 | 
						|
      }
 | 
						|
      return tmp
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  function getFont(obj) {
 | 
						|
    let fontfaces = []
 | 
						|
    if (obj["text-font"]) {
 | 
						|
      for (let wanted_face of obj["text-font"]) {
 | 
						|
        fontfaces.push({ face: wanted_face })
 | 
						|
      }
 | 
						|
    }
 | 
						|
    if (fontfaces.length === 0) fontfaces.push({ face: "sans-serif" })
 | 
						|
 | 
						|
    const text_size = obj["text-size"]
 | 
						|
 | 
						|
    if (text_size) {
 | 
						|
      if (typeof text_size == "number") {
 | 
						|
        return (z) => `${text_size}px ${fontfaces.map((f) => f.face).join(", ")}`
 | 
						|
      } else if (text_size.stops) {
 | 
						|
        let base = 1.4
 | 
						|
        if (text_size.base) base = text_size.base
 | 
						|
        else text_size.base = base
 | 
						|
        let t = numberFn(text_size)
 | 
						|
        return (z, f) => {
 | 
						|
          return `${t(z, f)}px ${fontfaces.map((f) => f.face).join(", ")}`
 | 
						|
        }
 | 
						|
      } else if (text_size[0] == "step") {
 | 
						|
        let t = numberFn(text_size)
 | 
						|
        return (z, f) => {
 | 
						|
          return `${t(z, f)}px ${fontfaces.map((f) => f.face).join(", ")}`
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    return (z) => "12px Arial Bold"
 | 
						|
  }
 | 
						|
 | 
						|
  //样式解析规则方法【重要】
 | 
						|
  function json_style(obj, sheet) {
 | 
						|
    let paint_rules = []
 | 
						|
    let label_rules = []
 | 
						|
    let refs = new Map()
 | 
						|
 | 
						|
    for (let layer of obj.layers) {
 | 
						|
      refs.set(layer.id, layer)
 | 
						|
 | 
						|
      if (layer.layout && layer.layout.visibility == "none") {
 | 
						|
        continue
 | 
						|
      }
 | 
						|
 | 
						|
      if (layer.ref) {
 | 
						|
        let referenced = refs.get(layer.ref)
 | 
						|
        layer.type = referenced.type
 | 
						|
        layer.filter = referenced.filter
 | 
						|
        layer.source = referenced["source"]
 | 
						|
        layer["source-layer"] = referenced["source-layer"]
 | 
						|
      }
 | 
						|
 | 
						|
      let sourceLayer = layer["source-layer"]
 | 
						|
 | 
						|
      let filter = undefined
 | 
						|
      if (layer.filter) {
 | 
						|
        filter = filterFn(layer.filter)
 | 
						|
      }
 | 
						|
 | 
						|
      // ignore background-color?
 | 
						|
      if (layer.type == "fill") {
 | 
						|
        const fillPattern = layer.paint["fill-pattern"]
 | 
						|
        const fill = layer.paint["fill-color"]
 | 
						|
        const opacity = layer.paint["fill-opacity"]
 | 
						|
        let pattern
 | 
						|
        if (fillPattern) {
 | 
						|
          const patternInfor = sheet.get(fillPattern)
 | 
						|
          const canvas = document.createElement("canvas")
 | 
						|
          canvas.width = patternInfor.w
 | 
						|
          canvas.height = patternInfor.h
 | 
						|
          const ctx = canvas.getContext("2d")
 | 
						|
          ctx.drawImage(sheet.canvas, patternInfor.x, patternInfor.y, patternInfor.w, patternInfor.h)
 | 
						|
          pattern = canvas
 | 
						|
        }
 | 
						|
        // 填充面
 | 
						|
        paint_rules.push({
 | 
						|
          dataLayer: layer["source-layer"],
 | 
						|
          filter: filter,
 | 
						|
          symbolizer: new protomaps.PolygonSymbolizer({
 | 
						|
            pattern,
 | 
						|
            fill,
 | 
						|
            opacity
 | 
						|
          })
 | 
						|
        })
 | 
						|
      } else if (layer.type == "fill-extrusion") {
 | 
						|
        // 用不同的填充来绘制填充挤出
 | 
						|
        // simulate fill-extrusion with plain fill
 | 
						|
        paint_rules.push({
 | 
						|
          dataLayer: layer["source-layer"],
 | 
						|
          filter: filter,
 | 
						|
          symbolizer: new protomaps.PolygonSymbolizer({
 | 
						|
            fill: layer.paint["fill-extrusion-color"],
 | 
						|
            opacity: layer.paint["fill-extrusion-opacity"]
 | 
						|
          })
 | 
						|
        })
 | 
						|
      } else if (layer.type == "line") {
 | 
						|
        const lineColorInfor = layer.paint["line-color"]
 | 
						|
        let lineColor
 | 
						|
        if (lineColorInfor.stops) {
 | 
						|
          lineColor = function (z, f) {
 | 
						|
            const stops = lineColorInfor.stops
 | 
						|
            const length = stops.length
 | 
						|
            for (let i = length - 1; i >= 0; i--) {
 | 
						|
              if (z < stops[i][0]) {
 | 
						|
                return stops[i][1]
 | 
						|
              }
 | 
						|
            }
 | 
						|
            return stops[length - 1][1]
 | 
						|
          }
 | 
						|
        } else {
 | 
						|
          lineColor = lineColorInfor
 | 
						|
        }
 | 
						|
        // 线
 | 
						|
        // simulate gap-width
 | 
						|
        if (layer.paint["line-dasharray"]) {
 | 
						|
          paint_rules.push({
 | 
						|
            dataLayer: layer["source-layer"],
 | 
						|
            filter: filter,
 | 
						|
            symbolizer: new protomaps.LineSymbolizer({
 | 
						|
              width: widthFn(layer.paint["line-width"], layer.paint["line-gap-width"]),
 | 
						|
              dash: layer.paint["line-dasharray"],
 | 
						|
              dashColor: lineColor
 | 
						|
            })
 | 
						|
          })
 | 
						|
        } else {
 | 
						|
          paint_rules.push({
 | 
						|
            dataLayer: layer["source-layer"],
 | 
						|
            filter: filter,
 | 
						|
            symbolizer: new protomaps.LineSymbolizer({
 | 
						|
              color: lineColor,
 | 
						|
              width: widthFn(layer.paint["line-width"], layer.paint["line-gap-width"])
 | 
						|
            })
 | 
						|
          })
 | 
						|
        }
 | 
						|
      } else if (layer.type == "symbol") {
 | 
						|
        let textField = layer.layout["text-field"]
 | 
						|
        if (textField) {
 | 
						|
          textField = textField.replace("{", "")
 | 
						|
          textField = textField.replace("}", "")
 | 
						|
        }
 | 
						|
        if (layer.layout["symbol-placement"] == "line") {
 | 
						|
          label_rules.push({
 | 
						|
            dataLayer: layer["source-layer"],
 | 
						|
            filter: filter,
 | 
						|
            symbolizer: new protomaps.LineLabelSymbolizer({
 | 
						|
              font: getFont(layer.layout),
 | 
						|
              fill: layer.paint["text-color"],
 | 
						|
              width: layer.paint["text-halo-width"],
 | 
						|
              stroke: layer.paint["text-halo-color"],
 | 
						|
              textTransform: layer.layout["text-transform"],
 | 
						|
              label_props: textField ? [textField] : undefined
 | 
						|
            })
 | 
						|
          })
 | 
						|
        } else if (layer.layout["icon-image"]) {
 | 
						|
          label_rules.push({
 | 
						|
            dataLayer: layer["source-layer"],
 | 
						|
            filter: filter,
 | 
						|
            symbolizer: new IconSymbolizer({
 | 
						|
              name: layer.layout["icon-image"],
 | 
						|
              sheet: sheet,
 | 
						|
              fill: layer.paint["text-color"],
 | 
						|
              stroke: layer.paint["text-halo-color"],
 | 
						|
              width: layer.paint["text-halo-width"],
 | 
						|
              label_props: textField ? [textField] : undefined
 | 
						|
            })
 | 
						|
          })
 | 
						|
        } else {
 | 
						|
          const textAnchor = layer.layout["text-anchor"]
 | 
						|
          let justify
 | 
						|
          switch (textAnchor) {
 | 
						|
            case "left":
 | 
						|
              justify = protomaps.Justify.Left
 | 
						|
              break
 | 
						|
            case "right":
 | 
						|
              justify = protomaps.Justify.Right
 | 
						|
              break
 | 
						|
            case "center":
 | 
						|
              justify = protomaps.Justify.Center
 | 
						|
              break
 | 
						|
          }
 | 
						|
          label_rules.push({
 | 
						|
            dataLayer: layer["source-layer"],
 | 
						|
            filter: filter,
 | 
						|
            symbolizer: new TextSymbolizer({
 | 
						|
              font: getFont(layer.layout),
 | 
						|
              fill: layer.paint["text-color"],
 | 
						|
              stroke: layer.paint["text-halo-color"],
 | 
						|
              width: layer.paint["text-halo-width"],
 | 
						|
              textTransform: layer.layout["text-transform"],
 | 
						|
              justify: justify,
 | 
						|
              label_props: textField ? [textField] : undefined
 | 
						|
            })
 | 
						|
          })
 | 
						|
        }
 | 
						|
      } else if (layer.type == "circle") {
 | 
						|
        // 圆
 | 
						|
        paint_rules.push({
 | 
						|
          dataLayer: layer["source-layer"],
 | 
						|
          filter: filter,
 | 
						|
          symbolizer: new protomaps.CircleSymbolizer({
 | 
						|
            radius: layer.paint["circle-radius"],
 | 
						|
            fill: layer.paint["circle-color"],
 | 
						|
            stroke: layer.paint["circle-stroke-color"],
 | 
						|
            width: layer.paint["circle-stroke-width"]
 | 
						|
          })
 | 
						|
        })
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    label_rules.reverse()
 | 
						|
    return { paint_rules: paint_rules, label_rules: label_rules, tasks: [] }
 | 
						|
  }
 | 
						|
 | 
						|
  class ArcGISPbfImageryProvider {
 | 
						|
    /**
 | 
						|
     * ArcGIS矢量切片图层加载器
 | 
						|
     * @constructor
 | 
						|
     * @alias ArcGISPbfImageryProvider
 | 
						|
     *
 | 
						|
     * @param {Object} options 具有以下参数的对象
 | 
						|
     * @param {string} options.url ArcGIS矢量切片图层服务地址
 | 
						|
     * @param {string} options.styleUrl ArcGIS矢量切片图层服务样式地址
 | 
						|
     * @param {number} [options.minimumLevel=0] 最小显示层级
 | 
						|
     * @param {number} [options.maximumLevel=26] 最大显示层级
 | 
						|
     * @param {number} [options.maximumNativeLevel=26] 矢量切片的最大切片层级
 | 
						|
     * @param {Cesium.Rectangle} [options.rectangle] 显示范围
 | 
						|
     */
 | 
						|
    constructor(options) {
 | 
						|
      options = Cesium.defaultValue(options, Cesium.defaultValue.EMPTY_OBJECT)
 | 
						|
 | 
						|
      this._url = options.url
 | 
						|
 | 
						|
      this._tileWidth = tileSize
 | 
						|
      this._tileHeight = tileSize
 | 
						|
      this._minimumLevel = Cesium.defaultValue(options.minimumLevel, 0)
 | 
						|
      this._maximumLevel = Cesium.defaultValue(options.maximumLevel, 26)
 | 
						|
      this._maximumNativeLevel = Cesium.defaultValue(options.maximumNativeLevel, this._maximumLevel)
 | 
						|
 | 
						|
      this._tilingScheme = options.tilingScheme || new Cesium.WebMercatorTilingScheme()
 | 
						|
 | 
						|
      this._rectangle = Cesium.defaultValue(options.rectangle, this._tilingScheme.rectangle)
 | 
						|
 | 
						|
      this._ready = false
 | 
						|
      this._readyPromise = Cesium.defer()
 | 
						|
 | 
						|
      const labelersCanvasContext = document.createElement("canvas").getContext("2d")
 | 
						|
 | 
						|
      this._paintRules = []
 | 
						|
      this._labelRules = []
 | 
						|
 | 
						|
      // 加载style文件
 | 
						|
      const styleResource = new Cesium.Resource(options.styleUrl)
 | 
						|
      styleResource.fetchJson()?.then((styleJson) => {
 | 
						|
        if (styleJson?.sources?.esri?.url) {
 | 
						|
          this._url = styleJson.sources.esri.url
 | 
						|
        }
 | 
						|
 | 
						|
        // 如果存在范围,则设置范围
 | 
						|
        const bounds = styleJson.sources?.esri?.bounds
 | 
						|
        if (bounds && !Cesium.defined(options.rectangle)) {
 | 
						|
          // 如果服务中存在 rectangle 但是没传入 rectangle 则使用服务中定义的
 | 
						|
          this._rectangle = Cesium.Rectangle.fromDegrees(bounds[0], bounds[1], bounds[2], bounds[3])
 | 
						|
        }
 | 
						|
 | 
						|
        const spritePNGResource = styleResource.getDerivedResource({
 | 
						|
          url: `${styleJson.sprite}.png`
 | 
						|
        })
 | 
						|
 | 
						|
        const spriteJsonResource = styleResource.getDerivedResource({
 | 
						|
          url: `${styleJson.sprite}.json`
 | 
						|
        })
 | 
						|
 | 
						|
        const image = new Image()
 | 
						|
        image.crossOrigin = "Anonymous"
 | 
						|
        image.onload = () => {
 | 
						|
          // 加载精灵图
 | 
						|
          const spriteCanvas = document.createElement("canvas")
 | 
						|
          spriteCanvas.width = image.width
 | 
						|
          spriteCanvas.height = image.height
 | 
						|
          const spriteCtx = spriteCanvas.getContext("2d")
 | 
						|
          spriteCtx.drawImage(image, 0, 0, image.width, image.height)
 | 
						|
 | 
						|
          spriteJsonResource.fetchJson()?.then((spriteIndex) => {
 | 
						|
            const sheet = new MarsSheet(spriteIndex, spriteCanvas)
 | 
						|
            this._ready = true
 | 
						|
            const rules = json_style(styleJson, sheet)
 | 
						|
            this._labelRules = rules.label_rules
 | 
						|
            this._paintRules = rules.paint_rules
 | 
						|
            this._labelers = new protomaps.Labelers(labelersCanvasContext, this._labelRules, 32, () => undefined)
 | 
						|
          })
 | 
						|
        }
 | 
						|
        image.src = spritePNGResource.url
 | 
						|
      })
 | 
						|
 | 
						|
      this._source = new protomaps.ZxySource(this._url + ArcGISPbfImageryProviderRequestTrailer, false)
 | 
						|
      let cache = new protomaps.TileCache(this._source, 1024)
 | 
						|
      this._view = new protomaps.View(cache, this._maximumNativeLevel, 2)
 | 
						|
 | 
						|
      this.pickFeaturesEvent = new Cesium.Event()
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * 图层服务的url
 | 
						|
     * @type {String}
 | 
						|
     * @readonly
 | 
						|
     */
 | 
						|
    get url() {
 | 
						|
      return this._url
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * 单块瓦片的宽度
 | 
						|
     * @type {Number}
 | 
						|
     * @readonly
 | 
						|
     */
 | 
						|
    get tileWidth() {
 | 
						|
      return this._tileWidth
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * 单块瓦片的高度
 | 
						|
     * @type {Number}
 | 
						|
     * @readonly
 | 
						|
     */
 | 
						|
    get tileHeight() {
 | 
						|
      return this._tileHeight
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * 最大显示层级
 | 
						|
     * @type {Number}
 | 
						|
     * @readonly
 | 
						|
     */
 | 
						|
    get maximumLevel() {
 | 
						|
      return this._maximumLevel
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * 最小显示层级
 | 
						|
     * @type {Number}
 | 
						|
     * @readonly
 | 
						|
     */
 | 
						|
    get minimumLevel() {
 | 
						|
      return this._minimumLevel
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * 矢量切片的最大加载层级
 | 
						|
     * @type {Number}
 | 
						|
     * @readonly
 | 
						|
     */
 | 
						|
    get maximumNativeLevel() {
 | 
						|
      return this._maximumNativeLevel
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * 当前使用的切片方案
 | 
						|
     * @type {Cesium.GeographicTilingScheme}
 | 
						|
     * @readonly
 | 
						|
     */
 | 
						|
    get tilingScheme() {
 | 
						|
      return this._tilingScheme
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * 显示范围
 | 
						|
     * @type {Cesium.Rectangle}
 | 
						|
     * @readonly
 | 
						|
     */
 | 
						|
    get rectangle() {
 | 
						|
      return this._rectangle
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * 是否准备完成
 | 
						|
     * @type {Boolean}
 | 
						|
     * @readonly
 | 
						|
     */
 | 
						|
    get ready() {
 | 
						|
      return this._ready
 | 
						|
    }
 | 
						|
 | 
						|
    get readyPromise() {
 | 
						|
      return this._readyPromise.promise
 | 
						|
    }
 | 
						|
 | 
						|
    get hasAlphaChannel() {
 | 
						|
      return true
 | 
						|
    }
 | 
						|
 | 
						|
    get credit() {
 | 
						|
      return this._credit
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Requests the image for a given tile.  This function should
 | 
						|
     * not be called before {@link ArcGISPbfImageryProvider#ready} returns true.
 | 
						|
     *
 | 
						|
     * @param {Number} x The tile X coordinate.
 | 
						|
     * @param {Number} y The tile Y coordinate.
 | 
						|
     * @param {Number} level The tile level.
 | 
						|
     * @returns {Promise.<ImageryTypes>|undefined} A promise for the image that will resolve when the image is available, or
 | 
						|
     *          undefined if there are too many active requests to the server, and the request should be retried later.
 | 
						|
     *
 | 
						|
     * @exception {DeveloperError} <code>requestImage</code> must not be called before the imagery provider is ready.
 | 
						|
     */
 | 
						|
    requestImage(x, y, level) {
 | 
						|
      const canvas = document.createElement("canvas")
 | 
						|
      canvas.width = this.tileWidth
 | 
						|
      canvas.height = this.tileHeight
 | 
						|
      try {
 | 
						|
        return this._renderTile({ x, y, z: level }, canvas)
 | 
						|
      } catch (e) {
 | 
						|
        return Promise.resolve(canvas)
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    _renderTile(coords, canvas) {
 | 
						|
      return this._view.getDisplayTile(coords).then((tile) => {
 | 
						|
        const tileMap = new Map().set("", [tile])
 | 
						|
 | 
						|
        this._labelers.add(coords.z, tileMap)
 | 
						|
 | 
						|
        let labelData = this._labelers.getIndex(tile.z)
 | 
						|
 | 
						|
        const bbox = {
 | 
						|
          minX: 256 * coords.x - BUF,
 | 
						|
          minY: 256 * coords.y - BUF,
 | 
						|
          maxX: 256 * (coords.x + 1) + BUF,
 | 
						|
          maxY: 256 * (coords.y + 1) + BUF
 | 
						|
        }
 | 
						|
        const origin = new MarsPoint(256 * coords.x, 256 * coords.y)
 | 
						|
 | 
						|
        const ctx = canvas.getContext("2d")
 | 
						|
        ctx.setTransform(this._tileWidth / 256, 0, 0, this._tileWidth / 256, 0, 0)
 | 
						|
        ctx.clearRect(0, 0, 256, 256)
 | 
						|
 | 
						|
        if (labelData) {
 | 
						|
          protomaps.painter(ctx, coords.z, tileMap, labelData, this._paintRules, bbox, origin, false, "")
 | 
						|
        }
 | 
						|
        return canvas
 | 
						|
      })
 | 
						|
    }
 | 
						|
 | 
						|
    pickFeatures(x, y, zoom, longitude, latitude) {
 | 
						|
      //目前不支持鼠标单击拾取,如果需要,需要此处加解析代码
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  class ArcGISPbfLayer extends mars3d.layer.BaseTileLayer {
 | 
						|
    //构建ImageryProvider
 | 
						|
    async _createImageryProvider(options) {
 | 
						|
      return createImageryProvider(options)
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  async function createImageryProvider(options) {
 | 
						|
    return new ArcGISPbfImageryProvider(options)
 | 
						|
  }
 | 
						|
  ArcGISPbfLayer.createImageryProvider = createImageryProvider
 | 
						|
 | 
						|
  //注册下
 | 
						|
  const layerType = "arcgis-pbf" //图层类型
 | 
						|
  mars3d.LayerUtil.register(layerType, ArcGISPbfLayer)
 | 
						|
  mars3d.LayerUtil.registerImageryProvider(layerType, createImageryProvider)
 | 
						|
 | 
						|
  //对外接口
 | 
						|
  mars3d.provider.ArcGISPbfImageryProvider = ArcGISPbfImageryProvider
 | 
						|
  mars3d.layer.ArcGISPbfLayer = ArcGISPbfLayer
 | 
						|
})(window)
 | 
						|
 | 
						|
//  { ArcGISPbfLayer }
 |