Commit 44ca7df6 authored by 郭铭瑶's avatar 郭铭瑶 🤘

添加地图组件

parent 54ca3f09
...@@ -10,6 +10,8 @@ ...@@ -10,6 +10,8 @@
<body> <body>
<div id="app"></div> <div id="app"></div>
<script src="/shanghaiwuye_gis_map_api_3.2.210421/SMap.min.js"></script>
<script src="/shanghaiwuye_gis_map_api_3.2.210421/Plugins.min.js"></script>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
</body> </body>
......
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
new SMap.Network().setNet(网络参数);
网络参数包括以下几个值:
'internet':表示互联网2D;
'affairs':表示政务网2D;
'local3D':表示局域网3D;
'affairs3D':表示政务网3D;
'njdl':表示南京东路政务网3D;
\ No newline at end of file
...@@ -72,7 +72,7 @@ html, body ...@@ -72,7 +72,7 @@ html, body
height 100% height 100%
overflow hidden overflow hidden
font-size .1rem font-size .1rem
background url('/src/assets/images/map.png') center/100% 100% no-repeat // background url('/src/assets/images/map.png') center/100% 100% no-repeat
color #fff color #fff
.orange-count .orange-count
font-size .14rem font-size .14rem
......
...@@ -64,6 +64,7 @@ export default defineComponent({ ...@@ -64,6 +64,7 @@ export default defineComponent({
background-size cover background-size cover
background-position center background-position center
height .4rem height .4rem
z-index 1
.date .date
z-index 2 z-index 2
position absolute position absolute
......
import { MapConfig, CallBack, BoundaryOption, MarkerOption } from './types'
import Map from './map'
declare const AMap: {
Map: any
Icon: any
DistrictSearch: any
LngLat: any
Marker: any
Polygon: any
}
export default class A_Map extends Map {
constructor(config: MapConfig) {
super(new AMap.Map(config.el, config.options))
}
onLoad(cb: CallBack): void {
this.map.on('complete', cb)
}
onZoom(cb: CallBack): void {
this.map.on('zoomend', cb)
}
onMove(cb: CallBack): void {
this.map.on('mapmove', cb)
}
onBlur(): void {
throw new Error('AMap没有blur事件!')
}
onFocus(): void {
throw new Error('AMap没有focus事件!')
}
onDrag(cb: CallBack): void {
this.map.on('dragging', cb)
}
onResize(cb: CallBack): void {
this.map.on('resize', cb)
}
onClick(cb: CallBack): void {
this.map.on('click', cb)
}
onDblclick(cb: CallBack): void {
this.map.on('dblclick', cb)
}
onMousewheel(cb: CallBack): void {
this.map.on('mousewheel', cb)
}
zoomIn(): void {
this.map.zoomIn()
}
zoomOut(): void {
this.map.zoomOut()
}
hideCommunity(): void {
throw new Error('AMap没有hideCommunity方法!')
}
showCommunity(): void {
throw new Error('AMap没有showCommunity方法!')
}
filterCommunity(): void {
throw new Error('AMap没有filterCommunity方法!')
}
focus(location: number[], zoom = this.map.getZoom()): void {
this.map.setView(location, zoom)
}
addBoundary({
name,
type,
weight = 10,
color = 'rgba(51,145,255,.6)',
maskColor = [0, 17, 33, 0.9],
}: BoundaryOption): unknown {
if (!name || !type) {
throw new Error('AMap的addBoundary方法需要name参数!')
return
}
return new AMap.DistrictSearch({
extensions: 'all',
subdistrict: 0,
}).search(name, (_: unknown, result: any) => {
// 外多边形坐标数组和内多边形坐标数组
const outer = [
new AMap.LngLat(-360, 90, true),
new AMap.LngLat(-360, -90, true),
new AMap.LngLat(360, -90, true),
new AMap.LngLat(360, 90, true),
]
const holes = result.districtList[0].boundaries
const pathArray = [outer]
pathArray.push(holes)
const polygon = new AMap.Polygon({
pathL: pathArray,
//线条颜色,使用16进制颜色代码赋值。默认值为#006600
strokeColor: color,
strokeWeight: weight,
//轮廓线透明度,取值范围[0,1],0表示完全透明,1表示不透明。默认为0.9
strokeOpacity: 0.5,
//多边形填充颜色,使用16进制颜色代码赋值,如:#FFAA00
fillColor: maskColor,
//多边形填充透明度,取值范围[0,1],0表示完全透明,1表示不透明。默认为0.9
fillOpacity: 1,
//轮廓线样式,实线:solid,虚线:dashed
strokeStyle: 'solid',
})
polygon.setPath(pathArray)
this.map.add(polygon)
return polygon
})
}
private transMarkerData(option: MarkerOption): unknown[] {
const { data = [], size, icon, key } = option
return data.map((item: any) => {
let sizeProp = Array.isArray(size) ? size : [size, size]
if (item.size) {
sizeProp = Array.isArray(item.size) ? item.size : [item.size, item.size]
}
const result = {
icon: new AMap.Icon({
size: sizeProp,
image: item.icon || icon,
}),
position: [
item.X || item.x || item.gpsx || item.lon,
item.Y || item.y || item.gpsy || item.lat,
item.Z || item.z || item.gpsz || 0,
],
extData: { key, ...item },
}
return new AMap.Marker(result)
})
}
addMarkers(option: MarkerOption): unknown {
const markers = this.transMarkerData(option)
this.map.add(markers)
return markers
}
remove(markers: unknown): void {
this.map.remove(markers)
}
}
import MyMap from '../my-map'
import {
CallBack,
MapConfig,
Listeners,
ControlOptions,
ZoomOptions,
FocusOptions,
Location,
ClickCallBack,
Controls,
AIMapConfig,
Layers,
LayerOption,
} from '../types'
import { toLines, toPoints, toPolygons } from './util'
/** 维智地图 */
export default class AI_Map extends MyMap {
private _clickCb: ClickCallBack | null = null
constructor(config: MapConfig<AIMapConfig>) {
if (!config.appKey) {
throw new Error('AIMap需要设置appKey参数!')
}
aimap.accessToken = config.appKey
if (!config.baseApiUrl) {
throw new Error('AIMap需要设置baseApiUrl参数!')
}
aimap.baseApiUrl = config.baseApiUrl
const instance = new aimap.Map({
container: config.el,
center: config.center,
zoom: config.zoom,
minZoom: config.zooms?.[0],
maxZoom: config.zooms?.[1],
pitch: config.pitch || 0,
minPitch: config.pitchs?.[0],
maxPitch: config.pitchs?.[1],
bearing: config.bearing || 0,
style: config.style,
localIdeographFontFamily: config.family,
})
super(instance)
this._setListeners()
this._setControls()
this._setLayers()
}
/** 设置监听事件 */
private _setListeners() {
const _listeners: Partial<Listeners> = {
load: (cb: CallBack) => {
this.map.on('load', cb)
},
zoom: (cb: CallBack) => {
this.map.on('zoom', cb)
},
move: (cb: CallBack) => {
this.map.on('move', cb)
},
blur: (cb: CallBack) => {
this.map.on('blur', cb)
},
focus: (cb: CallBack) => {
this.map.on('focus', cb)
},
drag: (cb: CallBack) => {
this.map.on('drag', cb)
},
resize: (cb: CallBack) => {
this.map.on('resize', cb)
},
click: (cb: ClickCallBack) => {
this._clickCb = cb
this.map.on('click', (e: any) => {
const arg = {
type: e.type,
center: [e.lngLat.lng, e.lngLat.lat],
event: e.originalEvent,
clientX: e.point.x,
clientY: e.point.y,
originData: e,
}
cb(arg)
})
},
}
this._listeners = Object.assign(this._listeners, _listeners)
}
/** 设置控制器 */
private _setControls() {
const _controls: Partial<Controls> = {
compass: (options?: ControlOptions) => {
this.map.addControl(new aimap.CompassControl(), options?.position)
},
zoom: (options?: ControlOptions) => {
this.map.addControl(
new aimap.NavigationControl({
showZoom: options?.show,
showCompass: false,
visualizePitch: false,
}),
options?.position,
)
},
scale: (options?: ControlOptions) => {
this.map.addControl(
new aimap.ScaleControl({
maxWidth: options?.maxWidth || 80,
unit: options?.unit || 'metric',
}),
options?.position,
)
},
}
this._controls = Object.assign(this._controls, _controls)
}
/** 设置覆盖物 */
private _setLayers() {
const makeLayer = (layer: any) => {
layer.on('click', (e: any) => {
const arg = {
type: e.type,
center: [e.lngLat.lng, e.lngLat.lat],
event: e.originalEvent,
clientX: e.point.x,
clientY: e.point.y,
originData: e,
}
// 点击覆盖物获取自定义数据
this._clickCb && this._clickCb(arg, e?.features?.[0]?.properties)
})
return layer
}
const _layers: Partial<Layers> = {
point: (data: LayerOption) =>
makeLayer(new aimap.MassMarkerLayer(toPoints(this.map, data))),
line: (data: LayerOption) =>
makeLayer(new aimap.LineString(toLines(this.map, data))),
polygon: (data: LayerOption) =>
makeLayer(new aimap.Polygon(toPolygons(this.map, data))),
}
this._layers = Object.assign(this._layers, _layers)
}
zoomIn(options: ZoomOptions) {
this.map.zoomIn(options)
}
zoomOut(options: ZoomOptions) {
this.map.zoomOut(options)
}
zoomTo(level: number, options: ZoomOptions) {
this.map.zoomTo(level, options)
}
focus(location: Location, options?: FocusOptions) {
this.map.flyTo({
center: location,
...options,
})
}
remove(layer: any) {
layer && layer.remove()
}
addBoundary() {
console.error('此地图不存在 addBoundary 方法')
return { remove: () => null }
}
}
import { LayerOption, PointsData } from '../types'
/** 转化点状覆盖物 */
export function toPoints(map: unknown, options: LayerOption) {
const {
data,
size = 20,
icon,
labelKey,
spatialReference = 'gcj02',
labelOptions,
} = options
const [horizontal, vertical] = labelOptions?.offset || []
const result: any = { data: [], images: [] }
const transData = (item: PointsData, i: number) => {
const x = item.X || item.x || item.gpsx
const y = item.Y || item.y || item.gpsy
if ((x !== 0 && !x) || (y !== 0 && !y)) {
console.error(`add point: 非法的坐标[${x}, ${y}] 存在于数据: ${item}`)
}
const z = item.Z || item.z || item.gpsz || 0
result.data.push({
...item,
id: item.id || i,
icon: item.icon ? `icon${i}` : 'icon',
spatialReference: spatialReference || item.spatialReference,
coordinates: z ? [x, y, z] : [x, y],
})
result.images.push({
id: item.icon ? `icon${i}` : 'icon',
url: item.icon || icon,
})
}
if (Array.isArray(data)) {
data.forEach(transData)
} else {
transData(data, 0)
}
return {
map,
spatialReference: spatialReference,
data: result.data,
images: result.images,
style: {
'text-field': `{${labelKey}}`,
'text-color': labelOptions?.color,
// 偏移相比测绘院地图大概小了10倍,且竖直偏移相反
'text-offset': [
horizontal ? horizontal / 10 : 0,
vertical ? vertical / -10 : 0,
],
'icon-anchor': 'center',
'icon-image': ['get', 'icon'],
// size相比测绘院地图大概小了50倍
'icon-size': Array.isArray(size) ? size[0] / 50 : size / 50,
},
}
}
/** 转化线状覆盖物 */
export function toLines(map: unknown, options: LayerOption) {
const {
data,
lineWidth = 3,
color = 'blue',
labelKey,
spatialReference = 'gcj02',
labelOptions,
lineJoin = 'round',
} = options
const [horizontal, vertical] = labelOptions?.offset || []
return {
map,
spatialReference: spatialReference,
data: {
coordinates: Array.isArray(data)
? data.map(({ path }) => path?.map((e) => e))
: data.path,
...data,
},
style: {
'line-width': lineWidth,
'line-color': color,
'line-join': lineJoin,
'text-field': `{${labelKey}}`,
'text-color': labelOptions?.color,
// 偏移相比测绘院地图大概小了10倍,且竖直偏移相反
'text-offset': [
horizontal ? horizontal / 10 : 0,
vertical ? vertical / -10 : 0,
],
},
}
}
/** 转化面状覆盖物 */
export function toPolygons(map: unknown, options: LayerOption) {
const {
data,
lineWidth = 3,
color = 'blue',
fillColor = 'rgba(135,206,235,0.5)',
labelKey,
spatialReference = 'gcj02',
labelOptions,
lineJoin = 'round',
} = options
const [horizontal, vertical] = labelOptions?.offset || []
return {
map,
spatialReference: spatialReference,
data: {
coordinates: Array.isArray(data)
? data.map(({ path }) => path?.map((e) => e))
: data.path,
...data,
},
style: {
'line-width': lineWidth,
'line-color': color,
'fill-color': fillColor,
'line-join': lineJoin,
'text-field': `{${labelKey}}`,
'text-color': labelOptions?.color,
// 偏移相比测绘院地图大概小了10倍,且竖直偏移相反
'text-offset': [
horizontal ? horizontal / 10 : 0,
vertical ? vertical / -10 : 0,
],
},
}
}
import { MapConfig, EventListener } from './types' import { AIMapConfig, MapConfig, SMapConfig } from './types'
import Map from './map' import type MapType from './my-map'
import S_Map from './s-map' import S_Map from './s-map'
import A_Map from './a-map' import AI_Map from './ai-map'
export type MapType = Omit<Map, keyof EventListener> const whichMap = {
SMap: {
export async function createMap(config: MapConfig): Promise<MapType> { /**
await _injectSource(config) * 使用配置初始化地图
return _whichMap(config) * @param config 地图配置
*/
with: (config: MapConfig<SMapConfig>) => new S_Map(config),
},
AIMap: {
/**
* 使用配置初始化地图
* @param config 地图配置
*/
with: (config: MapConfig<AIMapConfig>) => new AI_Map(config),
},
} }
function _injectSource(config: MapConfig): Promise<unknown[]> { export type MyMap = MapType
if (!config.sources || config.sources.length === 0) { export namespace MyMap {
return Promise.resolve([true]) /**
* 创建地图
* @param key 地图关键字:
*
* 'SMap': 测绘院地图
*
* 'AIMap': 维智地图
*
*/
export function useMap<K extends keyof typeof whichMap>(key: K) {
return whichMap[key]
} }
const promises = config.sources.map((source: string, index: number) => {
return new Promise((resolve) => {
const id = `_my_map_source${index}`
if (document.getElementById(id)) return resolve(true)
const mapSource = document.createElement('script')
mapSource.type = 'text/javascript'
mapSource.src = source
mapSource.setAttribute('id', id)
document.body.appendChild(mapSource)
mapSource.onload = () => resolve(true)
})
})
return Promise.all(promises)
} }
function _whichMap(config: MapConfig): Map { // function _injectSource(sources: any[]): Promise<unknown[]> {
const { type } = config // const promises = sources.map((source: string, index: number) => {
switch (type) { // return new Promise((resolve) => {
case 'SMap': // const id = `_my_map_source${index}`
return new S_Map(config) // if (document.getElementById(id)) return resolve(true)
case 'AMap':
return new A_Map(config) // const mapSource = document.createElement('script')
default: // mapSource.type = 'text/javascript'
if (type) { // mapSource.src = source
throw new Error(`不存在${type}类型的地图!`) // mapSource.setAttribute('id', id)
} else { // document.body.appendChild(mapSource)
// eslint-disable-next-line // mapSource.onload = () => resolve(true)
throw new Error('初始化地图需要配置\'type\'字段!') // })
} // })
} // return Promise.all(promises)
} // }
declare const SMap: {
Map: any
MapEvent: any
Size: any
Icon: any
Label: any
Marker: any
Polyline: any
Polygon: any
OverlayGroup: any
Network: any
Home: any
Zoom: any
Compass: any
Fullscreen: any
LayerListControl: any
MeasureLine: any
MeasureArea: any
BasemapToggle: any
UndergroundSwitch: any
BMapGallery: any
BMapGalleryExpand: any
LngLat: any
}
declare const Plugins: {
MaskBoundary: new (view: any) => {
add: (options: any) => void
remove: () => void
}
}
declare const aimap: {
Map: any
accessToken: string
baseApiUrl: string
NavigationControl: any
CompassControl: any
ScaleControl: any
MassMarkerLayer: any
LineString: any
Polygon: any
}
import {
CallBack,
EventListener,
BoundaryOption,
MarkerOption,
EventName,
} from './types'
export default abstract class Map implements EventListener {
protected map
constructor(map: any) {
this.map = map
}
get instance(): any {
return this.map
}
on<T extends keyof EventListener>(name: EventName, cb: CallBack): this {
const eventName = `on${name.slice(0, 1).toUpperCase()}${name
.slice(1)
.toLowerCase()}` as T
if (!this[eventName]) throw new Error(`地图不存在'${name}'事件`)
this[eventName](cb)
return this
}
abstract onLoad(cb: CallBack): void
abstract onZoom(cb: CallBack): void
abstract onMove(cb: CallBack): void
abstract onBlur(cb: CallBack): void
abstract onFocus(cb: CallBack): void
abstract onDrag(cb: CallBack): void
abstract onResize(cb: CallBack): void
abstract onClick(cb: CallBack): void
abstract onDblclick(cb: CallBack): void
abstract onMousewheel(cb: CallBack): void
abstract zoomIn(): void
abstract zoomOut(): void
abstract hideCommunity(): void
abstract showCommunity(): void
abstract filterCommunity(ids: unknown[], key: string): void
abstract focus(location: number[], zoom?: number): void
abstract addBoundary(option: BoundaryOption): unknown
abstract addMarkers(option: MarkerOption): unknown
abstract remove(markers: unknown): void
}
import {
CallBack,
ControlOptions,
Controls,
Listeners,
ZoomOptions,
FocusOptions,
Location,
Layers,
LayerOption,
BoundaryOption,
} from './types'
const defaultListeners = {
load: () => console.error('on:此地图不存在 load 监听事件!'),
zoom: () => console.error('on:此地图不存在 zoom 监听事件!'),
move: () => console.error('on:此地图不存在 move 监听事件!'),
blur: () => console.error('on:此地图不存在 blur 监听事件!'),
focus: () => console.error('on:此地图不存在 focus 监听事件!'),
drag: () => console.error('on:此地图不存在 drag 监听事件!'),
resize: () => console.error('on:此地图不存在 resize 监听事件!'),
click: () => console.error('on:此地图不存在 click 监听事件!'),
dblclick: () => console.error('on:此地图不存在 dblclick 监听事件!'),
mousewheel: () => console.error('on:此地图不存在 mousewheel 监听事件!'),
}
const defaultControls = {
home: () => console.error('set:此地图不存在 home 控件!'),
zoom: () => console.error('set:此地图不存在 home 控件!'),
compass: () => console.error('set:此地图不存在 home 控件!'),
scale: () => console.error('set:此地图不存在 scale 控件!'),
fullScreen: () => console.error('set:此地图不存在 fullScreen 控件!'),
layerList: () => console.error('set:此地图不存在 layerList 控件!'),
measureLine: () => console.error('set:此地图不存在 measureLine 控件!'),
measureArea: () => console.error('set:此地图不存在 measureArea 控件!'),
basemapToggle: () => console.error('set:此地图不存在 basemapToggle 控件!'),
underguroundSwitch: () =>
console.error('set:此地图不存在 underguroundSwitch 控件!'),
bMapGallery: () => console.error('set:此地图不存在 bMapGallery 控件!'),
bMapGalleryexpand: () =>
console.error('set:此地图不存在 bMapGalleryexpand 控件!'),
}
const defaultLayers = {
point: () => console.error('add:此地图不存在 point 覆盖物!'),
line: () => console.error('add:此地图不存在 line 覆盖物!'),
polygon: () => console.error('add:此地图不存在 polygon 覆盖物!'),
boundary: () => console.error('add:此地图不存在 boundary 覆盖物!'),
}
export default abstract class MyMap {
protected map
protected _listeners: Listeners = defaultListeners
protected _controls: Controls = defaultControls
protected _layers: Layers = defaultLayers
constructor(instance: any) {
this.map = instance
}
/**
* 原始地图实例
* (主要适用于调用原始地图的方法、功能)
*/
get instance(): any {
return this.map
}
/**
* 监听事件
* @param name 事件名称
* @param cb 回调函数
*/
on<K extends keyof Listeners>(name: K, cb: CallBack) {
this._listeners[name](cb)
return this
}
/**
* 添加控件
* @param name 控件名称
* @param options 控件参数:
*/
set<K extends keyof Controls>(name: K, options: ControlOptions = {}) {
if (options.show === undefined) options.show = true
if (options.position === undefined) options.position = 'top-right'
this._controls[name](options)
return this
}
/**
* 添加覆盖物
* @param type 覆盖物类型
* @param data 覆盖物数据
*/
add<K extends keyof Layers>(type: K, data: LayerOption) {
return this._layers[type](data)
}
/**
* zoom in
* @param options 参数
*/
abstract zoomIn(options?: ZoomOptions): void
/**
* zoom out
* @param options 参数
*/
abstract zoomOut(options?: ZoomOptions): void
/**
* zoom to
* @param level 等级
* @param options 参数
*/
abstract zoomTo(level: number, options?: ZoomOptions): void
/**
* 聚焦
* @param location 坐标
* @param options 参数
*/
abstract focus(location: Location, options?: FocusOptions): void
/**
* 移除覆盖物
* @param layer 覆盖物
*/
abstract remove(layer: unknown): void
/**
* 添加边界
* @param option 配置项
*/
abstract addBoundary(option: BoundaryOption): { remove: () => void }
}
import { MapConfig, CallBack, BoundaryOption, MarkerOption } from './types'
import Map from './map'
declare const SMap: {
Map: any
Network: any
MapEvent: any
Size: any
Icon: any
Label: any
Marker: any
}
declare const Plugins: {
MaskBoundary: any
}
export default class S_Map extends Map {
constructor(config: MapConfig) {
new SMap.Network().setNet(config.options.netType || 'internet')
super(new SMap.Map(config.el, config.options))
}
onLoad(cb: CallBack): void {
this.map.on(SMap.MapEvent.maploaded, cb)
}
onZoom(cb: CallBack): void {
this.map.on(SMap.MapEvent.zoomchanged, cb)
}
onMove(cb: CallBack): void {
this.map.on(SMap.MapEvent.centerchanged, cb)
}
private event(event: unknown, cb: CallBack) {
this.map.on(event, (view: any, eventParamter: unknown) => {
view.hitTest(eventParamter).then((res: unknown) => {
cb(res, eventParamter)
})
})
}
onBlur(cb: CallBack): void {
this.event(SMap.MapEvent.blur, cb)
}
onFocus(cb: CallBack): void {
this.event(SMap.MapEvent.focus, cb)
}
onDrag(cb: CallBack): void {
this.event(SMap.MapEvent.drag, cb)
}
onResize(cb: CallBack): void {
this.event(SMap.MapEvent.resize, cb)
}
onClick(cb: CallBack): void {
this.event(SMap.MapEvent.click, cb)
}
onDblclick(cb: CallBack): void {
this.event(SMap.MapEvent.doubleclick, cb)
}
onMousewheel(cb: CallBack): void {
this.event(SMap.MapEvent.mousewheel, cb)
}
zoomIn(): void {
this.map.zoomIn()
}
zoomOut(): void {
this.map.zoomOut()
}
hideCommunity(): void {
this.map.hideXQ_Poly()
this.map.hideMPZ()
this.map.hideXQ_Point()
}
showCommunity(): void {
this.map.showXQ_Poly()
this.map.showMPZ()
this.map.showXQ_Point()
}
filterCommunity(ids: unknown[], key = 'sect_id'): void {
if (ids.length === 0) return
const valid = ids.map((val: unknown) => {
return `${key} = '${val}'`
})
this.map.setFilter('wg_gis_xq_point', valid.join(' or '))
this.map.setFilter('wg_gis_mpz', valid.join(' or '))
this.map.setFilter('wg_gis_xq_poly', valid.join(' or '))
}
focus(location: number[], zoom = this.map.getZoom()): void {
this.map.setZoomAndCenter(zoom, location)
}
addBoundary({
name,
type,
weight = 10,
count = 10,
color = 'rgba(51,145,255,.6)',
maskColor = [0, 17, 33, 0.9],
}: BoundaryOption): unknown {
if (!name || !type) {
throw new Error('SMap的addBoundary方法需要name和type参数!')
return
}
const boundary = {
boundaryType: type,
boundaryDefinition: `name like '%${name}%'`,
boundarydistance: weight,
bounarycount: count,
boundaryColor: color,
maskColor: maskColor,
}
const Boundary = new Plugins.MaskBoundary(this.map.view)
Boundary.add(boundary)
return Boundary
}
private transMarkerData(option: MarkerOption): unknown[] {
const { data = [], size, icon, key, labelKey, color } = option
return data.map((item: any) => {
let sizeProp = Array.isArray(size)
? new SMap.Size(size[0], size[1])
: new SMap.Size(size, size)
if (item.size) {
sizeProp = Array.isArray(item.size)
? new SMap.Size(item.size[0], item.size[1])
: new SMap.Size(item.size, item.size)
}
const result = {
icon: new SMap.Icon({
size: sizeProp,
image: item.icon || icon,
}),
attributes: { key, ...item },
position: [
item.X || item.x || item.gpsx || item.lon,
item.Y || item.y || item.gpsy || item.lat,
item.Z || item.z || item.gpsz || 0,
],
label: new SMap.Label({
text: labelKey ? item[labelKey] + '' : '',
size,
color,
}),
}
return new SMap.Marker(result)
})
}
addMarkers(option: MarkerOption): unknown {
const markers = this.transMarkerData(option)
this.map.add(markers)
return markers
}
remove(markers: unknown): void {
this.map.remove(markers)
}
}
import MyMap from '../my-map'
import {
CallBack,
MapConfig,
Listeners,
ControlOptions,
Location,
FocusOptions,
Controls,
SMapConfig,
Layers,
LayerOption,
ClickCallBack,
BoundaryOption,
} from '../types'
import { toLines, toPoints, toPolygons } from './util'
/** 测绘院地图 */
export default class S_Map extends MyMap {
constructor(config: MapConfig<SMapConfig>) {
if (!config.netType) {
throw new Error('SMap需要设置netType参数!')
}
new SMap.Network().setNet(config.netType)
const instance = new SMap.Map(config.el, {
appKey: config.appKey,
viewMode: config.mode,
center: config.center,
zoom: config.zoom,
zooms: config.zooms,
pitch: config.pitch,
mapStyle: config.style,
showBuildingBlock: config.showBuildingBlock,
rotateEnable: config.rotateEnable,
})
super(instance)
this._setListeners()
this._setControls()
this._setLayers()
this.on('load', this._clearFooter)
}
/** 清除地图自带的脚注 */
private _clearFooter() {
const footer = document.querySelector(
'.esri-ui-manual-container>.esri-component',
)
footer && ((footer as HTMLElement).style.display = 'none')
}
/** 设置监听事件 */
private _setListeners() {
const _listeners: Partial<Listeners> = {
load: (cb: CallBack) => {
this.map.on(SMap.MapEvent.maploaded, cb)
},
zoom: (cb: CallBack) => {
this.map.on(SMap.MapEvent.zoomchanged, cb)
},
move: (cb: CallBack) => {
this.map.on(SMap.MapEvent.centerchanged, cb)
},
blur: (cb: CallBack) => {
this.map.on(SMap.MapEvent.blur, cb)
},
focus: (cb: CallBack) => {
this.map.on(SMap.MapEvent.focus, cb)
},
drag: (cb: CallBack) => {
this._addEvent(SMap.MapEvent.drag, cb)
},
resize: (cb: CallBack) => {
this.map.on(SMap.MapEvent.resize, cb)
},
click: (cb: CallBack) => {
this._addEvent(SMap.MapEvent.click, cb)
},
dblclick: (cb: CallBack) => {
this._addEvent(SMap.MapEvent.doubleclick, cb)
},
mousewheel: (cb: CallBack) => {
this._addEvent(SMap.MapEvent.mousewheel, cb)
},
}
this._listeners = Object.assign(this._listeners, _listeners)
}
/** 特殊的可能会带自定义数据的事件 */
private _addEvent(event: unknown, cb: ClickCallBack) {
this.map.on(event, (view: any, e: any) => {
const arg = {
type: e.type,
center: [e.mapPoint?.longitude, e.mapPoint?.latitude],
event: e.native,
clientX: e.x,
clientY: e.y,
x: e?.mapPoint?.x,
y: e?.mapPoint?.y,
originData: e,
}
view.hitTest(e).then((res: any) => {
cb(arg, res?.results?.[0]?.graphic?.attributes || null)
})
})
}
/** 设置控制器 */
private _setControls() {
const makeControl = (constructorFn: any, options?: ControlOptions) => {
this.map.addControl(
new constructorFn({
visible: options?.show,
position: options?.position,
}),
)
}
const _controls: Partial<Controls> = {
home: (options?: ControlOptions) => {
makeControl(SMap.Home, options)
},
compass: (options?: ControlOptions) => {
makeControl(SMap.Compass, options)
},
zoom: (options?: ControlOptions) => {
makeControl(SMap.Zoom, options)
},
fullScreen: (options?: ControlOptions) => {
makeControl(SMap.Fullscreen, options)
},
layerList: (options?: ControlOptions) => {
makeControl(SMap.LayerListControl, options)
},
measureLine: (options?: ControlOptions) => {
makeControl(SMap.MeasureLine, options)
},
measureArea: (options?: ControlOptions) => {
makeControl(SMap.MeasureArea, options)
},
basemapToggle: (options?: ControlOptions) => {
makeControl(SMap.BasemapToggle, options)
},
underguroundSwitch: (options?: ControlOptions) => {
makeControl(SMap.UndergroundSwitch, options)
},
bMapGallery: (options?: ControlOptions) => {
makeControl(SMap.BMapGallery, options)
},
bMapGalleryexpand: (options?: ControlOptions) => {
makeControl(SMap.BMapGalleryExpand, options)
},
}
this._controls = Object.assign(this._controls, _controls)
}
/** 设置覆盖物 */
private _setLayers() {
const makeLayer = (group: unknown[]) => {
const layer = new SMap.OverlayGroup(group, {})
this.map.add(layer)
return layer
}
const _layers: Partial<Layers> = {
point: (data: LayerOption) => makeLayer(toPoints(data)),
line: (data: LayerOption) => makeLayer(toLines(data)),
polygon: (data: LayerOption) => makeLayer(toPolygons(data)),
}
this._layers = Object.assign(this._layers, _layers)
}
zoomIn() {
this.map.zoomIn()
}
zoomOut() {
this.map.zoomOut()
}
zoomTo(level: number) {
this.map.setZoom(level)
}
focus(location: Location, options?: FocusOptions) {
const level = options?.zoom || this.map.getZoom()
this.map.setZoomAndCenter(level, location)
}
remove(layer: unknown) {
layer && this.map.remove(layer)
}
/** 添加边界 */
addBoundary({
name = '',
type = 'jd_boundary',
weight = 10,
count = 10,
color = 'rgba(51,145,255,.6)',
maskColor = [0, 17, 33, 0.9],
}: BoundaryOption) {
const boundary = new Plugins.MaskBoundary(this.map.view)
boundary.add({
boundaryType: type,
boundaryDefinition: `name like '%${name}%'`,
boundarydistance: weight,
bounarycount: count,
boundaryColor: color,
maskColor: maskColor,
})
return boundary
}
}
import { LayerOption, PointsData } from '../types'
/** 转化点状覆盖物 */
export function toPoints(options: LayerOption) {
const { data, size = 20, icon, labelKey, labelOptions } = options
const transData = (item: PointsData) => {
const iconSize = Array.isArray(size)
? new SMap.Size(...size)
: new SMap.Size(size, size)
const x = item.X || item.x || item.gpsx
const y = item.Y || item.y || item.gpsy
if ((x !== 0 && !x) || (y !== 0 && !y)) {
console.error(`add point: 非法的坐标[${x}, ${y}] 存在于数据: ${item}`)
}
const z = item.Z || item.z || item.gpsz || 0
const result: any = {
icon: {
size: iconSize,
image: item.icon || icon,
},
attributes: { ...item },
position: z ? [x, y, z] : [x, y],
}
if (labelKey) {
result.label = new SMap.Label({
text: item[labelKey] + '',
size: labelOptions?.size,
color: labelOptions?.color,
xoffset: labelOptions?.offset?.[0],
yoffset: labelOptions?.offset?.[1],
zoffset: labelOptions?.offset?.[2],
verticalAlignment: 'middle',
horizontalAlignment: 'center',
})
}
return new SMap.Marker(result)
}
if (Array.isArray(data)) {
return data.map(transData)
} else {
return [transData(data)]
}
}
/** 转化线状覆盖物 */
export function toLines(options: LayerOption) {
const {
data,
color = 'blue',
labelKey,
labelOptions,
lineCap = 'square',
lineStyle = 'solid',
lineJoin = 'round',
lineWidth = 1,
} = options
const transData = (item: PointsData) => {
const result: any = {
path: item.path?.map(([x, y]) => new SMap.LngLat(x, y)),
attributes: { ...item },
cap: lineCap,
strokeColor: color,
style: lineStyle,
lineJoin: lineJoin,
lineWidth, // 测绘院地图貌似没法设置线段粗细
}
if (labelKey) {
result.label = new SMap.Label({
text: item[labelKey] + '',
size: labelOptions?.size,
color: labelOptions?.color,
xoffset: labelOptions?.offset?.[0],
yoffset: labelOptions?.offset?.[1],
verticalAlignment: 'middle',
horizontalAlignment: 'center',
})
}
return new SMap.Polyline(result)
}
if (Array.isArray(data)) {
return data.map(transData)
} else {
return [transData(data)]
}
}
/** 转化面状覆盖物 */
export function toPolygons(options: LayerOption) {
const {
data,
color = 'blue',
fillColor = 'rgba(135,206,235,0.5)',
labelKey,
labelOptions,
lineCap = 'square',
lineStyle = 'solid',
lineJoin = 'round',
lineWidth = 3,
} = options
const transData = (item: PointsData) => {
const result: any = {
paths: item.path?.map(([x, y]) => new SMap.LngLat(x, y)),
attributes: { ...item },
cap: lineCap,
strokeColor: color,
fillColor,
strokeWeight: lineWidth,
style: lineStyle,
lineJoin: lineJoin,
}
if (labelKey) {
result.label = new SMap.Label({
text: item[labelKey] + '',
size: labelOptions?.size,
color: labelOptions?.color,
xoffset: labelOptions?.offset?.[0],
yoffset: labelOptions?.offset?.[1],
verticalAlignment: 'middle',
horizontalAlignment: 'center',
})
}
return new SMap.Polygon(result)
}
if (Array.isArray(data)) {
return data.map(transData)
} else {
return [transData(data)]
}
}
export interface EventListener { /**
onLoad: (cb: CallBack) => void * 地图配置
onZoom: (cb: CallBack) => void */
onMove: (cb: CallBack) => void export type MapConfig<T> = T & {
onBlur: (cb: CallBack) => void /** 地图容器(需要有宽高) */
onFocus: (cb: CallBack) => void el: string
onDrag: (cb: CallBack) => void /**
onResize: (cb: CallBack) => void * 在SMap中作为appKey使用
onClick: (cb: CallBack) => void * 在AIMap中作为accessToken使用
onDblclick: (cb: CallBack) => void */
onMousewheel: (cb: CallBack) => void appKey: string
} & Partial<{
/** 地图模式 */
mode: '2D' | '3D'
/** 地图初始中心点位 */
center: Location
/** 地图初始zoom等级 */
zoom: number
/** 地图zoom允许范围 */
zooms: [number, number]
/** 地图初始旋转角度 */
bearing: number
/** 地图初始仰角(3D用) */
pitch: number
/** 地图仰角允许范围 */
pitchs: [number, number]
/** 地图样式 */
style: string
showBuildingBlock: boolean
rotateEnable: boolean
family: string
}>
export type SMapConfig = {
/**
* SMap 地图专用
* @param internet 表示互联网2D
* @param affairs 表示政务网2D
* @param local3D 表示局域网3D
* @param affairs3D 表示政务网3D
* @param njdl 表示南京东路政务网3D
*/
netType: 'internet' | 'affairs' | 'local3D' | 'affairs3D' | 'njdl'
}
export type AIMapConfig = {
baseApiUrl: string
} }
export type EventName = export type Location = [number, number] | [number, number, number]
| 'load'
| 'zoom'
| 'move'
| 'blur'
| 'focus'
| 'drag'
| 'resize'
| 'click'
| 'dblclick'
| 'mousewheel'
export interface MapConfig { /**
el: string * 坐标系类型
type: 'SMap' | 'AMap' | 'BMap' * @param gcj02 高德坐标系
options: MapOptions * @param wgs84 gps坐标系
sources?: string[] * @param bd09 百度坐标系
* @param cgcs2000 城建坐标系
*/
export type LocationType = 'gcj02' | 'wgs84' | 'bd09' | 'cgcs2000'
/**
* 监听回调函数
*/
export type CallBack = (arg: any, oth?: any) => unknown
/** 经过封装的返回统一参数的点击回调 */
export type ClickCallBack = (
arg: {
/** 事件类型 */
type: string
/** 地图初始中心点位 */
center: number[]
/** 鼠标事件 */
event: PointerEvent
/** 触发时鼠标x位置 */
clientX: number
/** 触发时鼠标y位置 */
clientY: number
/** 鼠标点击x坐标 */
x?: number
/** 鼠标点击y坐标 */
y?: number
/** 地图触发事件的原始数据(即未经封装前的数据) */
originData: any
},
oth?: any,
) => unknown
/**
* 监听事件
*/
export interface Listeners {
/** 加载完成触发 */
load: (cb: CallBack) => unknown
/** zoom变化触发 */
zoom: (cb: CallBack) => unknown
/** 地图移动 */
move: (cb: CallBack) => unknown
/** 失焦触发 */
blur: (cb: CallBack) => unknown
/** 聚焦触发 */
focus: (cb: CallBack) => unknown
/** 拖动触发 */
drag: (cb: CallBack) => unknown
/** 视图大小变化触发 */
resize: (cb: CallBack) => unknown
/** 点击触发 */
click: (cb: ClickCallBack) => unknown
/** 双击触发 */
dblclick: (cb: CallBack) => unknown
/** 滚轮触发 */
mousewheel: (cb: CallBack) => unknown
}
type Position = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
/**
* 控制器选项
*/
export interface ControlOptions {
show?: boolean
/** 控件位置 */
position?: Position
/** ScaleControl控件的最大长度,以像素为单位 */
maxWidth?: number
/** ScaleControl控件的距离单位 */
unit?: 'imperial' | 'metric' | 'nautical'
}
/**
* 控制器列表
*/
export interface Controls {
home: (options?: ControlOptions) => void
compass: (options?: ControlOptions) => void
zoom: (options?: ControlOptions) => void
scale: (options?: ControlOptions) => void
fullScreen: (options?: ControlOptions) => void
layerList: (options?: ControlOptions) => void
measureLine: (options?: ControlOptions) => void
measureArea: (options?: ControlOptions) => void
basemapToggle: (options?: ControlOptions) => void
underguroundSwitch: (options?: ControlOptions) => void
bMapGallery: (options?: ControlOptions) => void
bMapGalleryexpand: (options?: ControlOptions) => void
}
/**
* zoom配置参数(维智地图用)
*/
export interface ZoomOptions {
/** 如果 false ,则没有动画效果(默认true) */
animate?: boolean
/** 动态转换的持续时间,按毫秒计算(默认3000) */
duration?: number
/** 该函数持续的时间在 0~1 之间, 返回一个表示状态的数字,初始状态为 0,最终状态为 1 */
easing?: (t?: number) => number | void
/** 动态转换结束后,目标中心与实际地图容器中心间的偏差,单位为像素 */
offset?: [number, number]
} }
export interface MapOptions { /**
viewMode?: '2D' | '3D' * focus配置参数(测绘院地图只有zoom有用)
center?: number[] */
zooms?: number[] export interface FocusOptions {
/**地图缩放等级(默认当前等级) */
zoom?: number zoom?: number
/** 地图倾斜角度(默认当前角度) */
pitch?: number pitch?: number
rotation?: number /** 地图旋转角度(默认当前角度) */
mapStyle?: string bearing?: number
showBuildingBlock?: boolean /** 图层距离容器四周距离(单位像素) */
resizeEnable?: boolean padding?: { top?: number; bottom?: number; left?: number; right?: number }
rotateEnable?: boolean /** 动画速度(默认1.2) */
pitchEnable?: boolean speed?: number
showLabel?: boolean /** 默认1.42 */
buildingAnimation?: boolean // 楼块出现是否带动画 curve?: number
expandZoomRange?: boolean /** 整个fly动画持续毫秒时间(默认3000) */
showIndoorMap?: boolean maxDuration?: number
features?: string[]
netType?: string
} }
export type CallBack = (arg: unknown, oth?: unknown) => void /**
* 覆盖物列表
*/
export interface Layers {
point: (data: LayerOption) => unknown
line: (data: LayerOption) => unknown
polygon: (data: LayerOption) => unknown
}
// export type CallBackDic = { [key: string]: CallBack } /** 覆盖物点位数据 */
export interface PointsData {
id?: string
x?: number
y?: number
z?: number
X?: number
Y?: number
Z?: number
gpsx?: number
gpsy?: number
gpsz?: number
icon?: string
spatialReference?: LocationType
path?: [number, number][]
[key: string]: any
}
/**
* 覆盖物选项
*/
export interface LayerOption {
data: PointsData | PointsData[]
size?: number | [number, number]
icon?: string
spatialReference?: LocationType
labelKey?: string
labelOptions?: {
size?: number
/** [水平偏移量,垂直偏移量] */
offset?: [number, number] | [number, number, number]
color?: string
}
color?: string
fillColor?: string
lineCap?: string
lineStyle?: string
lineJoin?: 'bevel' | 'round' | 'miter'
lineWidth?: number
}
/** 边界选项 */
export interface BoundaryOption { export interface BoundaryOption {
name?: string /** 根据type模糊匹配名称 */
name: string
/**
* 边界类型
*
* 'jd_boundary' : 街道
*
* 'jwh_boundary' : 南东工作站
*/
type?: string type?: string
weight?: number weight?: number
count?: number count?: number
color?: string color?: string
maskColor?: number[] maskColor?: number[]
} }
export interface MarkerOption {
data: unknown[]
key?: string
labelKey?: string
icon?: string
size?: number | number[]
color?: string
}
<template> <template>
<div ref="mapRef" class="map" /> <div id="container" />
<div class="btn">
<button @click="zoom('in')">zoom in</button>
<button @click="zoom('out')">zoom out</button>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, nextTick, onMounted } from 'vue' import icon1 from '@/assets/images/search.png'
import { createMap, MapType } from '@/components/my-map' import { MyMap } from '@/components/my-map'
import { nextTick, onMounted } from 'vue'
let map: MyMap
let pointsLayer: any
let linesLayer: any
let polygonLayer: any
let boundary: any
const mapRef = ref<any>(null)
let map: MapType
onMounted(async () => { onMounted(async () => {
await nextTick() await nextTick()
map = await createMap({
el: mapRef.value, map = MyMap.useMap('SMap').with({
type: 'SMap', el: 'container',
options: { center: [0, 0],
viewMode: '2D', zoom: 5,
center: [1019.614669, 54.167243], style: 'smap://styles/dark',
zooms: [7, 12], appKey: 'DTZ49ACE32PBN73GXPF085',
zoom: 8,
pitch: 45,
mapStyle: 'smap://styles/dark',
netType: 'internet', netType: 'internet',
},
sources: ['/SMap.min.js', '/Plugins.min.js'],
}) })
map.on('load', () => {
map.hideCommunity()
map map
.on('click', (a, b) => { .on('load', () => console.log('load'))
console.log('click', a, b) .on('click', (a, b) => console.log(a, b))
}) .on('zoom', (e) => console.log('zoom: ', e))
.on('dblclick', (a, b) => { .on('move', (e) => console.log('move: ', e))
console.log('dblclick', a, b) .on('blur', (e) => console.log('blur: ', e))
}) .on('focus', (e) => console.log('focus: ', e))
.on('zoom', (a, b) => { .on('drag', (e) => console.log('drag: ', e))
console.log('zoom', a, b) .on('resize', (e) => console.log('resize: ', e))
}) .on('dblclick', (e) => console.log('dblclick: ', e))
.on('focus', (a, b) => { .on('mousewheel', (e) => console.log('mousewheel: ', e))
console.log('focus', a, b)
})
.on('move', (a, b) => {
console.log('move', a, b)
})
.on('blur', (a, b) => {
console.log('blur', a, b)
})
.on('drag', (a, b) => {
console.log('drag', a, b)
})
.on('resize', (a, b) => {
console.log('resize', a, b)
})
.on('mousewheel', (a, b) => {
console.log('mousewheel', a, b)
})
})
}) })
const zoom = (type: string) => {
if (type === 'in') { function zoomIn() {
map.zoomIn() map.zoomIn()
map.showCommunity() }
} else { function zoomOut() {
map.zoomOut() map.zoomOut()
} }
function zoomTo() {
map.zoomTo(20)
}
function move() {
map.focus([121.59751247938203, 29.835174764721145], { zoom: 16 })
}
function addPoints() {
pointsLayer = map.add('point', {
data: [
{
x: 121.59751247938203,
y: 29.835174764721145,
text: 'test message',
title: 'icon',
},
],
size: 20,
icon: icon1,
labelKey: 'title',
labelOptions: {
size: 20,
offset: [0, 20],
color: '#fff',
},
})
}
function addLines() {
linesLayer = map.add('line', {
data: {
path: [
[0, 0],
[121.59751247938203, 29.835174764721145],
],
text: 'test message',
},
labelKey: 'text',
})
}
function addPolygon() {
polygonLayer = map.add('polygon', {
data: {
path: [
[0, 0],
[121.59751247938203, 29.835174764721145],
[21.59751247938203, 0.835174764721145],
[0, 0],
],
text: 'test message',
},
})
}
function removeLayers() {
map.remove(pointsLayer)
map.remove(linesLayer)
map.remove(polygonLayer)
boundary.remove()
} }
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.map #container
position fixed
width 100vw width 100vw
height 100vh height 100vh
.btn position fixed
position absolute top 0
right 20px left 0
top 20px
</style> </style>
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
columns="1fr 1.5fr 1fr" columns="1fr 1.5fr 1fr"
:rows="`${layout.rows}`" :rows="`${layout.rows}`"
> >
<Map />
<div <div
v-for="(_, index) in new Array(layout.boxNum)" v-for="(_, index) in new Array(layout.boxNum)"
:key="index" :key="index"
...@@ -31,7 +32,7 @@ ...@@ -31,7 +32,7 @@
</div> </div>
</m-modal> </m-modal>
</div> </div>
<MapBtns :isDefaultScreen="isDefaultScreen" @touch="handleMapBtnsEvents" /> <MapBtns :is-default-screen="isDefaultScreen" @touch="handleMapBtnsEvents" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
...@@ -41,6 +42,7 @@ import { onMounted, ref } from 'vue' ...@@ -41,6 +42,7 @@ import { onMounted, ref } from 'vue'
import { ajax, api } from '@/ajax' import { ajax, api } from '@/ajax'
import { NSpace, NButton } from 'naive-ui' import { NSpace, NButton } from 'naive-ui'
import MapBtns from './components/map-btns.vue' import MapBtns from './components/map-btns.vue'
import Map from './components/map.vue'
const { layout } = useLayout() const { layout } = useLayout()
const { components, componentList } = useComponent() const { components, componentList } = useComponent()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment