@vuemap/district-cluster
Version:
241 lines (233 loc) • 8.19 kB
text/typescript
/// <reference types="@vuemap/amap-jsapi-types" />
import { polygon } from '@turf/helpers'
import intersect from '@turf/intersect'
import utils from './utils'
import DistrictExplorer from './DistrictExplorer'
import BoundsItem from './BoundsItem'
import SphericalMercator from './SphericalMercator'
interface DeepCount {
total: number
count: number
}
export default class DistMgr {
_opts: any
_touchMap: any
singleCountryNode: any
isDistReady = false
nodeMap = {}
waitFnList: any[] = []
singleDistExplorer = new DistrictExplorer({})
constructor(opts) {
this._opts = utils.extend(
{
topAdcodes: [1e5]
},
opts
)
this._touchMap = {}
this.singleDistExplorer.loadAreaTree((error, areaTree) => {
if (error) throw error
this.filterAreaTree(areaTree)
this.singleCountryNode = areaTree
this.isDistReady = true
if (this.waitFnList.length) {
for (let i = 0, len = this.waitFnList.length; i < len; i++) {
this.waitFnList[i][0].call(this.waitFnList[i][1])
}
this.waitFnList.length = 0
}
// console.log('this._opts.topAdcodes: ', this._opts.topAdcodes)
this.singleDistExplorer.loadMultiAreaNodes(this._opts.topAdcodes)
})
}
pixelToLngLat(x, y, pz) {
return SphericalMercator.pointToLngLat([x, y], pz)
}
getBounds(node) {
const nodeBounds = node.bbounds
return new AMap.Bounds(
this.pixelToLngLat(nodeBounds.x, nodeBounds.y + nodeBounds.height, 20),
this.pixelToLngLat(nodeBounds.x + nodeBounds.width, nodeBounds.y, 20)
)
}
filterAreaTree(root) {
const stack = [root]
do {
const node = stack.pop()
this.nodeMap[node.adcode] = node
const bbox = node.bbox
node.bbounds = new BoundsItem(bbox[0], bbox[1], bbox[2], bbox[3])
node.bbox = this.getBounds(node)
if (node.children)
for (let children = node.children, i = 0, len = children.length; i < len; i++) {
children[i].childIdx = i
stack.push(children[i])
}
} while (stack.length)
}
isReady() {
return this.isDistReady
}
getParentAdcode(adcode, acroutes) {
if (!acroutes) {
const node = this.getNodeByAdcode(adcode)
if (!node) {
console.warn(`Can not find node: ${adcode}`)
return null
}
acroutes = node.acroutes
}
return acroutes && acroutes.length ? acroutes[acroutes.length - 1] : null
}
getSubIdx(subAdcode) {
return this.getNodeByAdcode(subAdcode).childIdx
}
getChildrenNum(adcode) {
const node = this.getNodeByAdcode(adcode)
return this.getChildrenNumOfNode(node)
}
getChildrenNumOfNode(node) {
return node.children ? node.children.length : node.childrenNum || 0
}
getNodeByAdcode(adcode) {
const node = this.nodeMap[adcode]
if (!node) {
let areaNode = this.singleDistExplorer.getLocalAreaNode(`${`${adcode}`.substr(0, 4)}00`)
areaNode || (areaNode = this.singleDistExplorer.getLocalAreaNode(`${`${adcode}`.substr(0, 2)}0000`))
if (!areaNode) return null
for (let subFeatures = areaNode.getSubFeatures(), i = 0, len = subFeatures.length; i < len; i++)
if (subFeatures[i].properties.adcode === adcode) return subFeatures[i].properties
}
return node
}
getNodeChildren(adcode) {
const node = this.getNodeByAdcode(adcode)
if (!node) return null
if (node.children) return node.children
if (node.childrenNum >= 0) {
const areaNode = this.singleDistExplorer.getLocalAreaNode(adcode)
if (!areaNode) return null
const children: any[] = [],
subFeatures = areaNode.getSubFeaturesInPixel()
for (let i = 0, len = subFeatures.length; i < len; i++) children.push(subFeatures[i].properties)
return children
}
return null
}
getExplorer() {
return this.singleDistExplorer
}
traverseCountry(bounds, zoom, handler, finish, thisArg) {
this.traverseNode(this.singleCountryNode, bounds, zoom, handler, finish, thisArg, [])
}
getNodeBoundsSize(node, zoom) {
const pz = this.getPixelZoom(),
scale = Math.pow(2, pz - zoom)
return [node.bbounds.width / scale, node.bbounds.height / scale]
}
doesRingRingIntersect(mapBounds: AMap.Bounds, bounds: AMap.Bounds) {
const mapArray = [
mapBounds.getNorthWest().toArray(),
mapBounds.getNorthEast().toArray(),
mapBounds.getSouthEast().toArray(),
mapBounds.getSouthWest().toArray(),
mapBounds.getNorthWest().toArray()
]
const boxArray = [
bounds.getNorthWest().toArray(),
bounds.getNorthEast().toArray(),
bounds.getSouthEast().toArray(),
bounds.getSouthWest().toArray(),
bounds.getNorthWest().toArray()
]
return !!intersect(polygon([mapArray]), polygon([boxArray]))
}
traverseNode(topNode, bounds: AMap.Bounds, zoom, handler, finish, thisArg, excludedAdcodes, deepCount?: DeepCount) {
if (!(excludedAdcodes && excludedAdcodes.indexOf(topNode.adcode) >= 0)) {
if (this.doesRingRingIntersect(bounds, topNode.bbox as AMap.Bounds)) {
const children = topNode.children,
hasChildren = children && children.length > 0
if (zoom > topNode.idealZoom && hasChildren) {
for (let i = 0, len = children.length; i < len; i++) {
this.traverseNode(children[i], bounds, zoom, handler, null, thisArg, excludedAdcodes)
}
} else handler.call(thisArg, topNode)
}
if (finish) {
if (deepCount) {
deepCount.count++
if (deepCount.count >= deepCount.total) {
finish.call(thisArg)
}
} else {
finish.call(thisArg)
}
}
}
}
onReady(fn, thisArg, canSync?: any) {
this.isDistReady
? canSync
? fn.call(thisArg)
: setTimeout(function () {
fn.call(thisArg)
}, 0)
: this.waitFnList.push([fn, thisArg])
}
getPixelZoom() {
return this.singleCountryNode?.pz
}
loadAreaNode(adcode, callback, thisArg, callSync) {
this.singleDistExplorer.loadAreaNode(adcode, callback, thisArg, callSync)
}
isExcludedAdcode(adcode) {
const excludedAdcodes = this._opts.excludedAdcodes
return excludedAdcodes && excludedAdcodes.indexOf(adcode) >= 0
}
traverseTopNodes(bounds: AMap.Bounds, zoom, handler, finish, thisArg) {
const topAdcodes = this._opts.topAdcodes,
excludedAdcodes = this._opts.excludedAdcodes,
deepCount: DeepCount = {
total: topAdcodes.length,
count: 0
}
for (let i = 0, len = topAdcodes.length; i < len; i++) {
const node = this.getNodeByAdcode(topAdcodes[i])
if (!node) throw new Error(`Can not find adcode: ${topAdcodes[i]}`)
this.traverseNode(node, bounds, zoom, handler, finish, thisArg, excludedAdcodes, deepCount)
}
}
tryClearCache(tag, maxLeft) {
if (!(maxLeft < 0)) {
const stack = [this.singleCountryNode],
list: any[] = [],
touchMap = this._touchMap
do {
const node = stack.pop()
node.children && utils.mergeArray(stack, node.children)
const exTag = touchMap[node.adcode]
exTag && exTag !== tag && list.push(node.adcode)
} while (stack.length)
list.sort(function (a, b) {
const diff = touchMap[a] - touchMap[b]
return 0 === diff ? a - b : diff
})
const toDelLen = list.length - maxLeft
if (!(toDelLen <= 0))
for (let i = 0; i < toDelLen; i++)
this.singleDistExplorer.clearAreaNodeCacheByAdcode(list[i]) && this.touchAdcode(list[i], null)
}
}
touchAdcode(adcode, tag) {
this._touchMap[adcode] = tag
}
destroy() {
this.singleDistExplorer.destroy()
this._touchMap = {}
this.nodeMap = {}
this.singleDistExplorer = undefined as any
this._opts = undefined
this.waitFnList = []
this.singleCountryNode = undefined
}
}