Vue 高德地圖 API Loca 如何使用 連線線圖層、脈衝連線圖層

KyleBing發表於2022-03-25

Vue 高德地圖 API Loca 如何使用 連線線圖層、脈衝連線圖層

閱讀此文你需要已經瞭解並掌握的:

  1. 已經會使用常規地圖的生成方式
  2. 已經瞭解如何載入 Loca 外掛
    如果不瞭解,可以檢視我之前的文章:
高德地圖 Vue 中 載入 資料視覺化 Loca 的方式
如何使用高德地圖 API 做一個路線規劃應用,展示自定義路線
高德地圖 API Loca 3D動畫的說明

最終實現的效果:

"感謝大哥送來的火箭"

在這裡插入圖片描述
請新增圖片描述

一、基礎知識

官方的連線線例子是一個與我國建交的國家連線圖
其中用到的兩個資料來源是:

建交的連線線資料: https://a.amap.com/Loca/static/static/diplomacy-line.json
建交的點資料:https://a.amap.com/Loca/static/static/diplomacy-point.json

1. 這個檢視中包含四個圖層:

  1. 一個盛放各省名稱的文字圖層 AMap.LabelsLayer
  2. 一個盛放各省座標點位置動畫的圖層 Loca.ScatterLayer
  3. 一個盛放目標點座標點位置的圖層 Loca.ScatterLayer
  4. 一個顯示脈衝連線線的圖層 Loca.PulseLinkLayer

2. 做一個這樣的檢視需要哪幾個步驟:

  1. 新建一個 map,並載入 Loca 外掛。
  2. 遍歷所有省份資料,生成省份名稱圖層 AMap.LabelsLayer
  3. 遍歷所有省份資料,生成省份地理座標標識圖層 Loca.ScatterLayer
  4. 生成目標點的標識圖層 Loca.ScatterLayer
  5. 遍歷所有省份資料,每個資料包【含目標點】、【當前省份座標點】兩個座標點的資料,根據兩個地點資料生成一條脈衝連線線。然後生成所有省份的連線線
  6. 最終使 Loca 的動畫動起來即可

3. 建立脈衝線需要了解的知識

脈衝線的建立過程是這樣的:

// 建立圖層
let pulseLayer = new Loca.PulseLinkLayer(圖層引數)
// 設定資料來源
pulseLayer.setSource(資料引數)
// 設定樣式
pulseLayer.setStyle(樣式引數)

4. 用到的資料如何生成

Loca 圖層接收的資料是 Loca.GeoJSONSource 格式的,而這個物件接收的內容格式是這樣的

let locaLayerData = new Loca.GeoJSONSource({
    data: {
        'type': 'FeatureCollection', // 固定
        'features': [ // 這裡就是點的陣列
            {
                'type': 'Feature',
                
                // 本例中我們用到的 geometry 有兩個格式,一種是 `Point` 一種是 `LineString`,
                'geometry': { 
                    'type': 'Point',
                    'coordinates': [121.504673, 25.046711], // 地理經緯度
                },
            },
        ],
    },
})

這是 LineString 格式時的 geometry 結構,

'geometry': { 
    'type': 'LineString ',
    'coordinates': [ // 地理經緯度,這裡有兩個經緯度值
        [121.504673, 25.046711], // 連線線的起點
        [121.504673, 25.046711]  // 連線線的終點
    ], 
},

瞭解了以上的結構,就可以根據自己需要來進行修改,生成資料了。
比如,我們得到官方的省份資料如下:

[
  { "name": "北京市", "center": "116.407394,39.904211" },
  { "name": "天津市", "center": "117.200983,39.084158" },
]

我們就可以根據這個生成點【座標層圖層資料】:

computed: {
    // 根據省份地址,生成展示地圖需要的格式化資料
    dataPoints(){
        let tempData = GEO_PROVINCE_DATA.map(item => {
            let co = item.center.split(',').map(item => Number(item)) // 將字串拆分成座標陣列資料
            return {
                "type": "Feature",
                "properties": {"province": item.name},
                "geometry": {
                    "type": "Point", // 點位
                    "coordinates": co
                }
            }
        })
        return {
            "type": "FeatureCollection",
            "features": tempData
        }
    },
    
    // 連線線圖層資料
    dataLines(){
        let tempData = GEO_PROVINCE_DATA.map(item => {
            let co = item.center.split(',').map(item => Number(item)) // 將字串拆分成座標陣列資料
            return {
                "type": "Feature",
                "properties": {"province": item.name},
                "geometry": {
                    "type": "LineString", // 線段
                    "coordinates": [
                        TARGET_POINT, // target location
                        co
                    ]
                }
            }
        })
        return {
            "type": "FeatureCollection",
            "features": tempData
        }
    },
}

使用的時候生成 geo 資料

const geoDataPoints = new Loca.GeoJSONSource({
    data: this.dataPoints,
})
const geoDataLines = new Loca.GeoJSONSource({
    data: this.dataLines,
})

根據這些資料載入圖層:

// 圖層點座標
let loadLocation = () => {
    setLabelsLayer(this.dataPoints)
    scatterLayer2.setSource(geoDataPoints)
    scatterLayer2.setStyle({
        size: [250000, 250000], // 點的大小
        unit: 'miter',
        animate: true,
        duration: 1000,
        texture: 'https://a.amap.com/Loca/static/static/orange.png',
        // texture: 'https://a.amap.com/Loca/static/static/green.png',
    })
    this.loca.add(scatterLayer2)

    // this.loca.animate.start() // 開始動畫
}
loadLocation()
// 連線線圖層
let linkLayer = new Loca.LinkLayer({
    zIndex: 20,
    opacity: 1,
    visible: true,
    zooms: [2, 22],
})
let loadLine = () => {
    linkLayer.setSource(geoDataLines)
    linkLayer.setStyle({
        lineColors: ['#ff7514', '#ff0008'],
        height: (index, item) => {
            return item.distance / 2
        },
        smoothSteps: 300
    })
    this.loca.add(linkLayer)
}
loadLine()
// pulse layer
let pulseLayer = new Loca.PulseLinkLayer({
    zIndex: 20,
    opacity: 1,
    visible: true,
    zooms: [2, 22],
})
let loadPulse = () => {
    pulseLayer.setSource(geoDataLinesReverse)
    pulseLayer.setStyle({
        height: (index, item) => {
            return item.distance / 2
        },
        unit: 'meter',
        dash: [40000, 0, 40000, 0],
        lineWidth: function () {
            return [20000, 2000]; // 始末 節點的線段寬度
        },
        // altitude: 1000,
        smoothSteps: 100, // 曲線圓滑度
        speed: function (index, prop) {
            return 1000 + Math.random() * 200000;
        },
        flowLength: 100000,
        lineColors: function (index, feat) {
            return ['rgb(255,221,0)', 'rgb(255,141,27)', 'rgb(65,0,255)'];
        },
        maxHeightScale: 0.3, // 弧頂位置比例
        headColor: 'rgba(255, 255, 0, 1)', // 線段中流動的
        trailColor: 'rgb(255,84,84)',
    })
    this.loca.add(pulseLayer)
}
loadPulse()

這些結束後,讓動畫動起來

this.map.on('complete', ()=> {
    this.loca.animate.start()
})

此時俯檢視是這個樣子:
在這裡插入圖片描述

再加一些 Loca 動畫,就成了這個樣子

在這裡插入圖片描述

備註:用到的官方資料、資源

各省會資料: https://a.amap.com/Loca/static/mock/districts.js
請新增圖片描述

中心點圖示: https://a.amap.com/Loca/static/static/center-point.png請新增圖片描述
動畫點(綠色):https://a.amap.com/Loca/static/static/green.png
請新增圖片描述
動畫點(橙色):https://a.amap.com/Loca/static/static/green.png
請新增圖片描述

完整程式碼:自行將 appID 替換成自己的

province.json

[
  { "name": "北京市", "center": "116.407394,39.904211" },
  { "name": "天津市", "center": "117.200983,39.084158" },
  { "name": "河北省", "center": "114.530235,38.037433" },
  { "name": "山西省", "center": "112.562678,37.873499" },
  { "name": "內蒙古自治區", "center": "111.76629,40.81739" },
  { "name": "遼寧省", "center": "123.431382,41.836175" },
  { "name": "吉林省", "center": "125.32568,43.897016" },
  { "name": "黑龍江省", "center": "126.661665,45.742366" },
  { "name": "上海市", "center": "121.473662,31.230372" },
  { "name": "江蘇省", "center": "118.762765,32.060875" },
  { "name": "浙江省", "center": "120.152585,30.266597" },
  { "name": "安徽省", "center": "117.329949,31.733806" },
  { "name": "福建省", "center": "119.295143,26.100779" },
  { "name": "江西省", "center": "115.81635,28.63666" },
  { "name": "山東省", "center": "117.019915,36.671156" },
  { "name": "河南省", "center": "113.753394,34.765869" },
  { "name": "湖北省", "center": "114.341745,30.546557" },
  { "name": "湖南省", "center": "112.9836,28.112743" },
  { "name": "廣東省", "center": "113.26641,23.132324" },
  { "name": "廣西壯族自治區", "center": "108.327546,22.815478" },
  { "name": "海南省", "center": "110.349228,20.017377" },
  { "name": "重慶市", "center": "106.551643,29.562849" },
  { "name": "四川省", "center": "104.075809,30.651239" },
  { "name": "貴州省", "center": "106.70546,26.600055" },
  { "name": "雲南省", "center": "102.710002,25.045806" },
  { "name": "西藏自治區", "center": "91.117525,29.647535" },
  { "name": "陝西省", "center": "108.954347,34.265502" },
  { "name": "甘肅省", "center": "103.826447,36.05956" },
  { "name": "青海省", "center": "101.780268,36.620939" },
  { "name": "寧夏回族自治區", "center": "106.259126,38.472641" },
  { "name": "新疆維吾爾自治區", "center": "87.627704,43.793026" },
  { "name": "香港特別行政區", "center": "114.171203,22.277468" },
  { "name": "澳門特別行政區", "center": "113.543028,22.186835" }
]

MapLoca.vue

<template>
    <div class="map-container">
        <div id="container" :style="`height: ${insets.height}px`"></div>
    </div>
</template>

<script>

import AMapLoader from '@amap/amap-jsapi-loader'
import {mapState} from "vuex"
import GEO_PROVINCE_DATA from './province.json'
import LaunchButton from "@/components/LaunnchButton";

let AMap = null

const TARGET_POINT = [121.504673, 25.046711] // 目標座標 臺灣
const DESTINATION_POINT = [110.504673, 28.046711] // 目標座標

export default {
    name: "MapLoca",
    components: {LaunchButton},
    data() {
        return {
            isLoading: false,
            contentHeight: 400,
            map: null,
            loca: null,
        }
    },
    mounted() {
        this.contentHeight = window.innerHeight

        AMapLoader.load({
            key: "替換成自己申請的高德web app id", // 開發應用的 ID
            version: "2.0",   // 指定要載入的 JSAPI 的版本,預設時預設為 1.4.15
            plugins: [],
            Loca:{
                version: '2.0.0',
            },
            AMapUI: {             // 是否載入 AMapUI,預設不載入
                version: '1.1',   // AMapUI 預設 1.1
                plugins: [],       // 需要載入的 AMapUI ui外掛
            },

        }).then(map => {
            AMap = map
            this.map = new AMap.Map('container', {
                viewMode: '3D',
                zoom: 6,
                pitch: 32,
                center: TARGET_POINT,
                mapStyle: 'amap://styles/grey',
                showBuildingBlock: true, // 顯示建築物
                showLabel: false, // 不顯示地名什麼的
            })


            // 文字圖層
            let labelLayer = new AMap.LabelsLayer({
                rejectMapMask: true,
                collision: true,
                animation: true,
            })
            this.map.add(labelLayer)

            this.loca = new Loca.Container({
                map: this.map,
            })



            let scatterLayer2 = new Loca.ScatterLayer({
                zIndex: 10,
                opacity: 0.8,
                visible: true,
                zooms: [2, 22],
            })
            let scatterLayer3 = new Loca.ScatterLayer({
                zIndex: 10,
                opacity: 0.8,
                visible: true,
                zooms: [2, 22],
            })

            let centerPoint = new Loca.GeoJSONSource({
                data: {
                    'type': 'FeatureCollection',
                    'features': [
                        {
                            'type': 'Feature',
                            'geometry': {
                                'type': 'Point',
                                'coordinates': TARGET_POINT,
                            },
                        },
                    ],
                },
            })
            scatterLayer3.setSource(centerPoint)
            scatterLayer3.setStyle({
                size: [300000, 300000],
                unit: 'meter',
                texture: 'https://a.amap.com/Loca/static/static/center-point.png',
            })
            this.loca.add(scatterLayer3)

            let lineGeoMap
            let scatterGeoMap


            let setLabelsLayer = (data) => {
                labelLayer.clear()
                data.features.forEach((item) => {
                    let labelsMarker = new AMap.LabelMarker({
                        name: item.properties.province,
                        position: item.geometry.coordinates,
                        zooms: [2, 22],
                        opacity: 1,
                        zIndex: 10,
                        text: {
                            content: item.properties.province,
                            direction: 'bottom',
                            offset: [0, -5],
                            style: {
                                fontSize: 13,
                                fontWeight: 'normal',
                                fillColor: '#fff',
                            },
                        },
                    })
                    labelLayer.add(labelsMarker)
                })
                labelLayer.add(
                    new AMap.LabelMarker({
                        name: '臺灣',
                        position: TARGET_POINT,
                        zooms: [2, 22],
                        opacity: 1,
                        zIndex: 10,
                        rank: 100,
                        text: {
                            content: '臺灣',
                            direction: 'bottom',
                            offset: [0, -5],
                            style: {
                                fontSize: 13,
                                fontWeight: 'normal',
                                fillColor: '#fff',
                            },
                        },
                    }),
                )
            }

            const geoDataPoints = new Loca.GeoJSONSource({
                data: this.dataPoints,
            });
            const geoDataLines = new Loca.GeoJSONSource({
                data: this.dataLines,
            });
            const geoDataLinesReverse = new Loca.GeoJSONSource({
                data: this.dataLinesReverse,
            });


            let loadLocation = () => {
                setLabelsLayer(this.dataPoints)
                scatterLayer2.setSource(geoDataPoints)
                scatterLayer2.setStyle({
                    size: [250000, 250000],
                    unit: 'miter',
                    animate: true,
                    duration: 1000,
                    texture: 'https://a.amap.com/Loca/static/static/orange.png',
                    // texture: 'https://a.amap.com/Loca/static/static/green.png',
                })
                this.loca.add(scatterLayer2)

                // this.loca.animate.start() // 開始動畫
            }
            loadLocation()

            let linkLayer = new Loca.LinkLayer({
                zIndex: 20,
                opacity: 1,
                visible: true,
                zooms: [2, 22],
            })
            let loadLine = () => {
                linkLayer.setSource(geoDataLines)
                linkLayer.setStyle({
                    lineColors: ['#ff7514', '#ff0008'],
                    height: (index, item) => {
                        return item.distance / 2
                    },
                    smoothSteps: 300
                })
                this.loca.add(linkLayer)
            }
            // loadLine()

            // pulse layer
            let pulseLayer = new Loca.PulseLinkLayer({
                zIndex: 20,
                opacity: 1,
                visible: true,
                zooms: [2, 22],
            })
            let loadPulse = () => {
                pulseLayer.setSource(geoDataLinesReverse)
                pulseLayer.setStyle({
                    height: (index, item) => {
                        return item.distance / 2
                    },
                    unit: 'meter',
                    dash: [40000, 0, 40000, 0],
                    lineWidth: function () {
                        return [20000, 2000]; // 始末 節點的線段寬度
                    },
                    // altitude: 1000,
                    smoothSteps: 100, // 曲線圓滑度
                    speed: function (index, prop) {
                        return 1000 + Math.random() * 200000;
                    },
                    flowLength: 100000,
                    lineColors: function (index, feat) {
                        return ['rgb(255,221,0)', 'rgb(255,141,27)', 'rgb(65,0,255)'];
                    },
                    maxHeightScale: 0.3, // 弧頂位置比例
                    headColor: 'rgba(255, 255, 0, 1)',
                    trailColor: 'rgb(255,84,84)',
                })
                this.loca.add(pulseLayer)
            }
            loadPulse()

            this.animateStart()

            this.map.on('complete', ()=> {
                this.loca.animate.start()
            })

        }).catch(e => {
            console.log(e)
        })
    },

    computed: {
        ...mapState(['insets']),
        // 根據省份地址,生成展示地圖需要的格式化資料
        dataPoints(){
            let tempData = GEO_PROVINCE_DATA.map(item => {
                let co = item.center.split(',').map(item => Number(item))
                return {
                    "type": "Feature",
                    "properties": {"province": item.name},
                    "geometry": {
                        "type": "Point", // 點位
                        "coordinates": co
                    }
                }
            })
            return {
                "type": "FeatureCollection",
                "features": tempData
            }
        },
        dataLines(){
            let tempData = GEO_PROVINCE_DATA.map(item => {
                let co = item.center.split(',').map(item => Number(item))
                return {
                    "type": "Feature",
                    "properties": {"province": item.name},
                    "geometry": {
                        "type": "LineString", // 線段
                        "coordinates": [
                            TARGET_POINT, // target location
                            co
                        ]
                    }
                }
            })
            return {
                "type": "FeatureCollection",
                "features": tempData
            }
        },
        dataLinesReverse(){
            let tempData = GEO_PROVINCE_DATA.map(item => {
                let co = item.center.split(',').map(item => Number(item))
                return {
                    "type": "Feature",
                    "properties": {"province": item.name},
                    "geometry": {
                        "type": "LineString", // 線段
                        "coordinates": [
                            co,
                            TARGET_POINT // target location
                        ]
                    }
                }
            })
            return {
                "type": "FeatureCollection",
                "features": tempData
            }
        },
    },
    methods: {
        animateStart(){
            this.loca.viewControl.addAnimates([{
                    center: {
                        value: DESTINATION_POINT, // 動畫終點的經緯度
                        control: [TARGET_POINT, DESTINATION_POINT], // 過渡中的軌跡控制點,地圖上的經緯度
                        timing: [0.42, 0, 0.4, 1], // 動畫時間控制點
                        duration: 5000, // 過渡時間,毫秒(ms)
                    },
                    // 俯仰角動畫
                    pitch: {
                        value: 60, // 動畫終點的俯仰角度
                        control: [[0, 0], [1, 60]], // 控制器,x是0~1的起始區間,y是pitch值
                        timing: [0, 0, 1, 1], // 這個值是線性過渡
                        duration: 5000,
                    },
                    // 縮放等級動畫
                    zoom: {
                        value: 5, // 動畫終點的地圖縮放等級
                        control: [[0, 8], [1, 5]], // 控制器,x是0~1的起始區間,y是zoom值
                        timing: [0, 0, 1, 1],
                        duration: 8000,
                    },
                    // 旋轉動畫
                    rotation: {
                        value: -30, // 動畫終點的地圖旋轉角度
                        control: [[0, 0], [1, -30]], // 控制器,x是0~1的起始區間,y是rotation值
                        timing: [0, 0, 1, 1],
                        duration: 8000,
                    }
                }],
                () => {})
        },
        resizeMap() {
            let mapContainer = document.getElementById('container')
            mapContainer.style.height = window.innerHeight + "px"
            mapContainer.style.width = window.innerWidth + "px"
        },
     },
    beforeUnmount() {
        this.loca.destroy() // 需要先銷燬 Loca 再銷燬 Map
        this.map.destroy() // 銷燬地圖,釋放記憶體
        this.map = null
    }
}
</script>

<style lang="scss" scoped>
.map-container {
    position: relative;
}

</style>

相關文章