@vuemap/district-cluster
Version:
220 lines (214 loc) • 7.48 kB
text/typescript
/// <reference types="@vuemap/amap-jsapi-types" />
import Const from './Const'
import bbIdxBuilder from './bbIdxBuilder'
import SphericalMercator from './SphericalMercator'
import geomUtils from './geomUtils'
export default class AreaNode {
adcode?: any
_data?: any
_sqScaleFactor?: any
_opts?: any
_sqNearTolerance?: any
constructor(adcode, data, opts) {
this.adcode = adcode
this._data = data
this._sqScaleFactor = data.scale * data.scale
this._opts = Object.assign(
{
nearTolerance: 2
},
opts
)
this.setNearTolerance(this._opts.nearTolerance)
}
static getPropsOfFeature(f) {
return f && f.properties ? f.properties : null
}
static getAdcodeOfFeature(f) {
return f ? f.properties.adcode : null
}
static doesFeatureHasChildren(f) {
return !!f && f.properties.childrenNum > 0
}
setNearTolerance(t) {
this._opts.nearTolerance = t
this._sqNearTolerance = t * t
}
getIdealZoom() {
return this._data.idealZoom
}
_getEmptySubFeatureGroupItem(idx) {
return {
subFeatureIndex: idx,
subFeature: this.getSubFeatureByIndex(idx),
pointsIndexes: [],
points: []
}
}
groupByPosition(points, getPosition) {
let i,
len,
groupMap = {},
outsideItem = null
for (i = 0, len = points.length; i < len; i++) {
const idx = this.getLocatedSubFeatureIndex(getPosition.call(null, points[i], i))
groupMap[idx] || (groupMap[idx] = this._getEmptySubFeatureGroupItem(idx))
groupMap[idx].pointsIndexes.push(i)
groupMap[idx].points.push(points[i])
idx < 0 && (outsideItem = groupMap[idx])
}
const groupList: any[] = []
if (this._data.geoData.sub)
for (i = 0, len = this._data.geoData.sub.features.length; i < len; i++)
groupList.push(groupMap[i] || this._getEmptySubFeatureGroupItem(i))
outsideItem && groupList.push(outsideItem)
groupMap = null as any
return groupList
}
getLocatedSubFeatureIndex(lngLat) {
return this._getLocatedSubFeatureIndexByPixel(this.lngLatToPixel(lngLat))
}
getSubFeatureByIndex(fIdx) {
if (fIdx >= 0) {
const features = this.getSubFeatures()
return features[fIdx]
}
return null
}
_getLocatedSubFeatureIndexByPixel(pixel) {
if (!this._data.geoData.sub) return -1
const data = this._data,
bbIdx = data.bbIndex,
offX = pixel[0] - bbIdx.l,
offY = pixel[1] - bbIdx.t,
y = Math.floor(offY / bbIdx.s),
x = Math.floor(offX / bbIdx.s)
if (x < 0 || y < 0 || y >= bbIdx.h || x >= bbIdx.w) return -1
const seqIdx = y * bbIdx.w + x,
idxItem = bbIdx.idxList[seqIdx]
if (!idxItem) return -1
const BBRFLAG = Const.BBRFLAG
switch (idxItem[0]) {
case BBRFLAG.I:
return idxItem[1]
case BBRFLAG.S:
// bbIdxBuilder.prepareGridFeatureClip(data, x, y, idxItem[1])
bbIdxBuilder.prepareGridFeatureClip(data, x, y)
return this._calcLocatedFeatureIndexOfSList(pixel, idxItem[1])
default:
throw new Error(`Unknown BBRFLAG: ${idxItem[0]}`)
}
}
_calcNearestFeatureIndexOfSList(pixel, list) {
let features: any[] = []
this._data.geoData.sub && (features = this._data.geoData.sub.features)
const closest = {
sq: Number.MAX_VALUE,
idx: -1
}
for (let i = 0, len = list.length; i < len; i++) {
const idxItem = list[i],
feature = features[idxItem[0]],
ring = idxItem[2] || feature.geometry.coordinates[idxItem[1]][0],
sqDistance = geomUtils.sqClosestDistanceToPolygon(pixel, ring)
if (sqDistance < closest.sq) {
closest.sq = sqDistance
closest.idx = idxItem[0]
}
}
return closest.sq / this._sqScaleFactor < this._sqNearTolerance ? closest.idx : -1
}
_calcLocatedFeatureIndexOfSList(pixel, list) {
for (let features = this._data.geoData.sub.features, i = 0, len = list.length; i < len; i++) {
const idxItem = list[i],
feature = features[idxItem[0]],
ring = idxItem[2] || feature.geometry.coordinates[idxItem[1]][0]
if (geomUtils.pointInPolygon(pixel, ring) || geomUtils.pointOnPolygon(pixel, ring)) return idxItem[0]
}
return this._calcNearestFeatureIndexOfSList(pixel, list)
}
pixelToLngLat(x, y) {
return SphericalMercator.pointToLngLat([x, y], this._data.pz)
}
lngLatToPixel(lngLat) {
lngLat instanceof AMap.LngLat && (lngLat = [lngLat.getLng(), lngLat.getLat()])
const pMx = SphericalMercator.lngLatToPoint(lngLat, this._data.pz)
return [Math.round(pMx[0]), Math.round(pMx[1])]
}
_convertRingCoordsToLngLats(ring) {
const list: any[] = []
for (let i = 0, len = ring.length; i < len; i++) list[i] = this.pixelToLngLat(ring[i][0], ring[i][1])
return list
}
_convertPolygonCoordsToLngLats(poly) {
const list: any[] = []
for (let i = 0, len = poly.length; i < len; i++) list[i] = this._convertRingCoordsToLngLats(poly[i])
return list
}
_convertMultiPolygonCoordsToLngLats(polys) {
const list: any[] = []
for (let i = 0, len = polys.length; i < len; i++) list[i] = this._convertPolygonCoordsToLngLats(polys[i])
return list
}
_convertCoordsToLngLats(type, coordinates) {
switch (type) {
case 'MultiPolygon':
return this._convertMultiPolygonCoordsToLngLats(coordinates)
default:
throw new Error(`Unknown type ${type}`)
}
}
_createLngLatFeature(f, extraProps?: any) {
const newNode = Object.assign({}, f)
extraProps && Object.assign(newNode.properties, extraProps)
newNode.geometry = Object.assign({}, newNode.geometry)
newNode.geometry.coordinates = this._convertCoordsToLngLats(newNode.geometry.type, newNode.geometry.coordinates)
return newNode
}
getAdcode() {
return this.getProps('adcode')
}
getName() {
return this.getProps('name')
}
getChildrenNum() {
return this.getProps('childrenNum')
}
getProps(key) {
const props = AreaNode.getPropsOfFeature(this._data.geoData.parent)
return props ? (key ? props[key] : props) : null
}
getParentFeature() {
const geoData = this._data.geoData
geoData.lngLatParent || (geoData.lngLatParent = this._createLngLatFeature(geoData.parent))
return geoData.lngLatParent
}
getParentFeatureInPixel() {
return this._data.geoData.parent
}
getSubFeatures() {
const geoData = this._data.geoData
if (!geoData.sub) return []
if (!geoData.lngLatSubList) {
const newFList: any[] = []
for (let features = geoData.sub.features, i = 0, len = features.length; i < len; i++)
newFList[i] = this._createLngLatFeature(features[i])
geoData.lngLatSubList = newFList
}
return [].concat(geoData.lngLatSubList)
}
getSubFeaturesInPixel() {
return this._data.geoData.sub ? [].concat(this._data.geoData.sub.features) : []
}
getBounds() {
const data = this._data
if (!data.lngLatBounds) {
const nodeBounds = this._data.bounds
data.lngLatBounds = new AMap.Bounds(
this.pixelToLngLat(nodeBounds.x, nodeBounds.y + nodeBounds.height),
this.pixelToLngLat(nodeBounds.x + nodeBounds.width, nodeBounds.y)
)
}
return data.lngLatBounds
}
}