最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Node 切片拼接及地圖導(dǎo)出實(shí)例詳解

 更新時(shí)間:2022年08月22日 16:22:11   作者:牛老師講GIS  
這篇文章主要為大家介紹了Node 切片拼接及地圖導(dǎo)出實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

概述

本文講述在node中,使用canvas實(shí)現(xiàn)根據(jù)出圖范圍和級(jí)別,拼接瓦片并疊加geojson矢量數(shù)據(jù),并導(dǎo)出成圖片。

實(shí)現(xiàn)效果

實(shí)現(xiàn)

1. 初始化工程

通過命令npm init -y初始化工程并添加對(duì)應(yīng)的依賴,最終的package.json文件如下:

{
  "name": "map",
  "version": "1.0.0",
  "description": "",
  "main": "map.js",
  "scripts": {
    "map": "node ./map.js"
  },
  "keywords": ["canvas", "map"],
  "author": "lzugis<niujp08@qq.com>",
  "license": "ISC",
  "dependencies": {
    "canvas": "^2.9.3",
    "proj4": "^2.8.0",
    "ora": "^5.4.0"
  }
}

2. 編寫工具類

canvas.js,canvas操作工具,主要實(shí)現(xiàn)canvas畫布初始化,并實(shí)現(xiàn)了添加圖片 、繪制點(diǎn)、繪制線、繪制面等方法。

const { createCanvas, loadImage } = require('canvas')
class CanvasUtil {
  constructor(width = 1000, height = 1000) {
    this.canvas = createCanvas(width, height)
    this.ctx = this.canvas.getContext('2d')
  }
  /**
   * 繪制多個(gè)圖片
   * @param imgsData, [{url: '', x: '', y: ''}]
   * @return {Promise<unknown>}
   */
  drawImages(imgsData) {
    const that = this
    let promises = []
    imgsData.forEach(data => {
      promises.push(new Promise(resolve => {
        loadImage(data.url).then(img => {
            resolve({
              ...data,
              img
            })
        })
      }))
    })
    return new Promise(resolve => {
      Promise.all(promises).then(imgDatas => {
        imgDatas.forEach(imgData => {
          that.drawImage(imgData.img, imgData.x, imgData.y)
        })
        resolve(imgDatas)
      })
    })
  }
  /**
   * 繪制一張圖片
   * @param image
   * @param x
   * @param y
   * @param width
   * @param height
   */
  drawImage(image, x, y, width, height) {
    const that = this
    width = width || image.width
    height = height || image.height
    that.ctx.drawImage(image, x, y, width, height)
  }
  /**
   * 繪制多個(gè)點(diǎn)
   * @param pointsData,[{type: 'circle', size: 4, x: 100, y: 100, icon: ''}]
   */
  drawPoints(pointsData = []) {
    const that = this
    return new Promise(resolve => {
      let promises = []
      pointsData.forEach(pointData => {
        that.ctx.beginPath()
        that.ctx.save()
        that.ctx.fillStyle = pointData.color || 'rgba(255, 0, 0, 1)'
        const type = pointData.type || 'circle'
        const size = pointData.size || 4
        let {x, y} = pointData
        pointData.x = x
        pointData.y = y
        switch (type) {
          case "rect": {
            x -= size
            y -= size
            that.ctx.fillRect(x, y, size * 2, size * 2)
            promises.push(Promise.resolve(pointData))
            break
          }
          case "circle": {
            that.ctx.arc(x, y, size, 0, Math.PI * 2)
            that.ctx.fill()
            promises.push(Promise.resolve(pointData))
            break
          }
          case "marker": {
            promises.push(new Promise(resolve1 => {
              loadImage(pointData.icon).then(img => {
                const w = img.width * pointData.size
                const h = img.height * pointData.size
                x -= w / 2
                y -= h / 2
                that.drawImage(img, x, y, w, h)
                resolve(pointData)
              })
            }))
            break
          }
        }
        that.ctx.restore()
      })
      Promise.all(promises).then(res => {
        resolve({
          code: '200'
        })
      })
    })
  }
  /**
   * 繪制線
   * @param linesData [{}]
   * @return {Promise<unknown>}
   */
  drawLines(linesData) {
    const that = this
    return new Promise(resolve => {
      linesData.forEach(lineData => {
        that.ctx.beginPath()
        that.ctx.save()
        that.ctx.strokeStyle = lineData.color || 'red'
        that.ctx.lineWidth = lineData.width || 2
        that.ctx.setLineDash(lineData.dasharray || [5, 0]);
        lineData.coords.forEach((coord, index) => {
          const [x, y] = coord
          index === 0 ? that.ctx.moveTo(x, y) : that.ctx.lineTo(x, y)
        })
        that.ctx.stroke()
        that.ctx.restore()
      })
      resolve({
        code: '200'
      })
    })
  }
  /**
   * 繪制多邊形
   * @param polygonsData
   * @return {Promise<unknown>}
   */
  drawPolygons(polygonsData) {
    const that = this
    return new Promise(resolve => {
      polygonsData.forEach(polygonData => {
        that.ctx.beginPath()
        that.ctx.save()
        polygonData.coords.forEach((coord, index) => {
          const [x, y] = coord
          index === 0 ? that.ctx.moveTo(x, y) : that.ctx.lineTo(x, y)
        })
        that.ctx.closePath()
        if(polygonData.isFill) {
          that.ctx.fillStyle = polygonData.fillStyle || 'rgba(255, 0, 0,  0.2)'
          that.ctx.fill()
        }
        if(polygonData.isStroke) {
          that.ctx.strokeStyle = polygonData.strokeStyle || 'red'
          that.ctx.lineWidth =  polygonData.lineWidth || 2
          that.ctx.setLineDash(polygonData.lineDash || [5, 0]);
          that.ctx.stroke()
        }
        that.ctx.restore()
      })
      resolve({
        code: '200'
      })
    })
  }
  /**
   * 獲取canvas數(shù)據(jù)
   * @return {string}
   */
  getDataUrl() {
    return this.canvas.toDataURL().replace(/^data:image\/\w+;base64,/, '')
  }
  /**
   * 添加標(biāo)題
   * @param title
   */
  addTitle(title) {
    this.ctx.save()
    this.ctx.strokeStyle = '#fff'
    this.ctx.lineWidth = 3
    this.ctx.fillStyle = '#fff'
    let x = 20, y = 20, offset = 8
    let h = 32
    this.ctx.font = `bold ${h}px 微軟雅黑`
    this.ctx.textAlign = 'left'
    this.ctx.textBaseline = 'top'
    let w = this.ctx.measureText(title).width
    // 外邊框
    this.ctx.strokeRect(x, y, offset * 4 + w, offset * 4 + h)
    // 內(nèi)邊框
    this.ctx.strokeRect(x + offset, y + offset, offset * 2 + w, offset * 2 + h)
    // 文字
    this.ctx.fillText(title, x + offset * 2, y + offset * 2)
    this.ctx.restore()
  }
}
module.exports = CanvasUtil

tile.js,切片操作工具,提供了坐標(biāo)轉(zhuǎn)換的方法、獲取范圍內(nèi)的切片的行列范圍、地理坐標(biāo)轉(zhuǎn)換為屏幕坐標(biāo)等方法。

const proj4 = require('proj4')
const { randomNum } = require('./common')
class TileUtil {
  constructor(tileSize = 256) {
    this.tileSize = tileSize
    this.origin = 20037508.34
    this.resolutions = []
    let resolution = (this.origin * 2) / this.tileSize
    for (let i = 0; i < 23; i++) {
      this.resolutions.push(resolution)
      resolution /= 2
    }
    this.tileUrl = 'https://webst0{domain}.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}'
  }
  /**
   * 4326轉(zhuǎn)3857
   * @param lonlat
   * @return {*}
   */
  fromLonLat(lonlat) {
    return proj4('EPSG:4326', 'EPSG:3857', lonlat)
  }
  /**
   * 3857轉(zhuǎn)4326
   * @param coords
   * @return {*}
   */
  toLonLat(coords) {
    return proj4('EPSG:3857', 'EPSG:4326', coords)
  }
  /**
   * 獲取范圍內(nèi)的切片的行列號(hào)的范圍
   * @param zoom
   * @param extent
   * @return {number[]}
   */
  getTilesInExtent(zoom, extent) {
    extent = this.getExtent(extent)
    const [xmin, ymin, xmax, ymax] = extent
    const res = this.resolutions[zoom] * 256
    const xOrigin = -this.origin, yOrigin = this.origin
    const _xmin = Math.floor((xmin - xOrigin) / res)
    const _xmax = Math.ceil((xmax - xOrigin) / res)
    const _ymin = Math.floor((yOrigin - ymax) / res)
    const _ymax = Math.ceil((yOrigin - ymin) / res)
    return [_xmin, _ymin, _xmax, _ymax]
  }
  /**
   * 獲取切片地址
   * @param x
   * @param y
   * @param z
   * @return {string}
   */
  getTileUrl(x, y, z) {
    let url = this.tileUrl.replace(/\{x\}/g, x)
    url = url.replace(/\{y\}/g, y)
    url = url.replace(/\{z\}/g, z)
    return url.replace(/\{domain\}/g, randomNum())
  }
  /**
   * 獲取切片大小
   * @return {number}
   */
  getTileSize() {
    return this.tileSize
  }
  /**
   * 地理坐標(biāo)轉(zhuǎn)換為屏幕坐標(biāo)
   * @param extent
   * @param zoom
   * @param lonLat
   * @return {*[]}
   */
  project(extent, zoom, lonLat) {
    const [xmin, ymin, xmax, ymax] = this.getTilesInExtent(zoom, extent)
    const res = this.resolutions[zoom]
    const resMap = this.tileSize * res
    const topLeft = [
      resMap * xmin - this.origin,
      this.origin - resMap * ymin
    ]
    const coords = this.fromLonLat(lonLat)
    const x = (coords[0] - topLeft[0]) / res
    const y = (topLeft[1] - coords[1]) / res
    return [x, y]
  }
  /**
   * 處理四至
   * @param extent
   * @return {*[]}
   */
  getExtent(extent) {
    if(Boolean(extent)) {
      const min = this.fromLonLat([extent[0], extent[1]])
      const max = this.fromLonLat([extent[2], extent[3]])
      extent = [...min, ...max]
    } else {
      extent = [-this.origin, -this.origin, this.origin, this.origin]
    }
    return extent
  }
  /**
   * 判斷是否在范圍內(nèi)
   * @param extent
   * @param lonLat
   * @return {boolean}
   */
  isInExtent(extent, lonLat) {
    const [xmin, ymin, xmax, ymax] = extent
    const [lon, lat] = lonLat
    return lon >= xmin && lon <= xmax && lat >=ymin && lat <= ymax
  }
}
module.exports = TileUtil

map.js,實(shí)現(xiàn)地圖導(dǎo)出,會(huì)用到前面提到的兩個(gè)工具類。

const fs = require('fs');
const ora = require('ora'); // loading
const TileUtil = require('./utils/tile')
const CanvasUtil = require('./utils/canvas')
const spinner = ora('tile joint').start()
const tileUtil = new TileUtil()
const z = 5
// const extent = undefined
const extent = [73.4469604492187500,6.3186411857604980,135.0858306884765625,53.5579261779785156]
const [xmin, ymin, xmax, ymax] = tileUtil.getTilesInExtent(z, extent)
const width = (xmax - xmin) * tileUtil.getTileSize()
const height = (ymax - ymin) * tileUtil.getTileSize()
const canvasUtil = new CanvasUtil(width, height)
let urls = []
for(let i = xmin; i < xmax; i++) {
  const x = (i - xmin) * tileUtil.getTileSize()
  for(let j = ymin; j < ymax; j++) {
    const y = (j - ymin) * tileUtil.getTileSize()
    const url = tileUtil.getTileUrl(i, j, z)
    urls.push({
      i, j, x, y, url
    })
  }
}
// 添加點(diǎn)數(shù)據(jù)
function addCapitals() {
  let geojson = fs.readFileSync('./data/capital.json')
  geojson = JSON.parse(geojson)
  let pointsData = []
  geojson.features.forEach(feature => {
    const coords = feature.geometry.coordinates
    if(!extent || tileUtil.isInExtent(extent, coords)) {
      const [x, y] = tileUtil.project(extent, z, coords)
      const { name } = feature.properties
      if(name === '北京') {
        pointsData.push({type: 'marker', size: 0.35, x, y, icon: './icons/icon-star.png'})
      } else {
        pointsData.push({type: 'rect', size: 4, x, y, color: '#ff0'})
      }
    }
  })
  return canvasUtil.drawPoints(pointsData)
}
// 添加線數(shù)據(jù)
function addLines() {
  let geojson = fs.readFileSync('./data/boundry.json')
  geojson = JSON.parse(geojson)
  let linesData = []
  geojson.features.forEach(feature => {
    const {type, coordinates} = feature.geometry
    if(type === 'LineString') {
      linesData.push({
        width: 2,
        color: 'rgba(255,0,0,0.8)',
        coords: coordinates.map(coords => {
          return tileUtil.project(extent, z, coords)
        })
      })
    } else {
      coordinates.forEach(_coordinates => {
        linesData.push({
          width: 2,
          color: 'rgba(255,0,0,0.8)',
          coords: _coordinates.map(coords => {
            return tileUtil.project(extent, z, coords)
          })
        })
      })
    }
  })
  return canvasUtil.drawLines(linesData)
}
// 添加面數(shù)據(jù)
function addPolygons() {
  let geojson = fs.readFileSync('./data/province.geojson')
  geojson = JSON.parse(geojson)
  let polygonsData = []
  geojson.features.forEach(feature => {
    const { name } = feature.properties
    const {type, coordinates} = feature.geometry
    if(type === 'Polygon') {
      const coords = coordinates[0].map(coords => {
        return tileUtil.project(extent, z, coords)
      })
      polygonsData.push({
        isStroke: true,
        isFill: true,
        lineWidth: 1,
        lineDash: [5, 5],
        strokeStyle: 'rgb(240,240,240)',
        fillColor: 'rgba(255, 0, 0,  0.1)',
        coords,
        name
      })
    } else {
      coordinates[0].forEach(_coordinates => {
        const coords = _coordinates.map(coords => {
          return tileUtil.project(extent, z, coords)
        })
        polygonsData.push({
          isStroke: true,
          isFill: true,
          lineWidth: 1,
          lineDash: [5, 5],
          strokeStyle: 'rgb(240,240,240)',
          fillStyle: 'rgba(255, 0, 0,  0.1)',
          coords,
          name
        })
      })
    }
  })
  return canvasUtil.drawPolygons(polygonsData)
}
// 1.拼接切片,2.添加面數(shù)據(jù),3.添加點(diǎn)數(shù)據(jù),4.添加線數(shù)據(jù),5.導(dǎo)出圖片
canvasUtil.drawImages(urls).then(() => {
  addPolygons().then((a) => {
    addCapitals().then(() => {
      addLines().then(() => {
        canvasUtil.addTitle('中國省級(jí)區(qū)劃圖')
        let base64Data = canvasUtil.getDataUrl()
        let dataBuffer = Buffer.from(base64Data, 'base64')
        fs.writeFileSync(`./result/map${z}.png`, dataBuffer)
        spinner.succeed()
        spinner.color = 'green'
      })
    })
  })
})

本文源代碼請(qǐng)掃描下面二維碼或直接前往倉庫獲取。

以上就是Node 切片拼接及地圖導(dǎo)出實(shí)例詳解的詳細(xì)內(nèi)容,更多關(guān)于Node 切片拼接 地圖導(dǎo)出的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • node.js實(shí)現(xiàn)簡(jiǎn)單的壓縮/解壓縮功能示例

    node.js實(shí)現(xiàn)簡(jiǎn)單的壓縮/解壓縮功能示例

    這篇文章主要介紹了node.js實(shí)現(xiàn)簡(jiǎn)單的壓縮/解壓縮功能,結(jié)合實(shí)例形式分析了node.js實(shí)現(xiàn)本地文件與服務(wù)器端壓縮/解壓縮相關(guān)操作技巧,需要的朋友可以參考下
    2019-11-11
  • 利用node.js如何創(chuàng)建子進(jìn)程詳解

    利用node.js如何創(chuàng)建子進(jìn)程詳解

    之前看多進(jìn)程這一章節(jié)時(shí)發(fā)現(xiàn)這塊東西挺多,寫Process模塊的時(shí)候也有提到,今天下午午休醒來靜下心來好好的看了一遍,發(fā)現(xiàn)也不是太難理解。所以下面這篇文章主要給大家介紹了關(guān)于利用node.js如何創(chuàng)建子進(jìn)程的相關(guān)資料,需要的朋友可以參考下。
    2017-12-12
  • nodejs socket服務(wù)端和客戶端簡(jiǎn)單通信功能

    nodejs socket服務(wù)端和客戶端簡(jiǎn)單通信功能

    這篇文章主要為大家詳細(xì)介紹了nodejs socket服務(wù)端和客戶端簡(jiǎn)單通信功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-09-09
  • 詳解使用PM2管理nodejs進(jìn)程

    詳解使用PM2管理nodejs進(jìn)程

    本篇文章主要介紹了詳解使用PM2管理nodejs進(jìn)程,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-10-10
  • npm安裝時(shí)的錯(cuò)誤排查的方法

    npm安裝時(shí)的錯(cuò)誤排查的方法

    在我們的日常工作中,使用npm來安裝依賴是非常常見的,然而,有時(shí)候安裝過程中會(huì)遇到各種各樣的報(bào)錯(cuò),本文主要介紹了npm安裝時(shí)的錯(cuò)誤排查的方法,感興趣的可以了解一下
    2024-08-08
  • socket.io學(xué)習(xí)教程之基本應(yīng)用(二)

    socket.io學(xué)習(xí)教程之基本應(yīng)用(二)

    socket.io提供了基于事件的實(shí)時(shí)雙向通訊,下面這篇文章主要給大家介紹了socket.io基本應(yīng)用的相關(guān)資料,對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧。
    2017-04-04
  • nodejs操作mysql實(shí)現(xiàn)增刪改查的實(shí)例

    nodejs操作mysql實(shí)現(xiàn)增刪改查的實(shí)例

    下面小編就為大家?guī)硪黄猲odejs操作mysql實(shí)現(xiàn)增刪改查的實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-05-05
  • Nodejs全??蚣躍trongLoop推薦

    Nodejs全??蚣躍trongLoop推薦

    StrongLoop基本提供了制作一個(gè)移動(dòng)產(chǎn)品所有的框架和工具,從標(biāo)準(zhǔn)的Backend server,Devops,應(yīng)用監(jiān)控,。要想介紹完全StrongLoop的所有產(chǎn)品得寫一個(gè)長篇連載了,這里只簡(jiǎn)單的瀏覽一遍。
    2014-11-11
  • node.js處理前端提交的GET請(qǐng)求

    node.js處理前端提交的GET請(qǐng)求

    這篇文章主要為大家詳細(xì)介紹了node.js處理前端提交的GET請(qǐng)求,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-08-08
  • nodejs高版本降為低版本的詳細(xì)解決方案

    nodejs高版本降為低版本的詳細(xì)解決方案

    部分老舊項(xiàng)目需要使用低版本的node,網(wǎng)上很多是無效的,高版本無法直接安裝低版本node,但是低版本nodejs可以安裝部分高版本node,從而達(dá)到升級(jí)效果,下面這篇文章主要給大家介紹了關(guān)于nodejs高版本降為低版本的詳細(xì)解決方案,需要的朋友可以參考下
    2022-12-12

最新評(píng)論

志丹县| 康马县| 景洪市| 九江县| 乌什县| 郎溪县| 高淳县| 乾安县| 太康县| 宝坻区| 岳池县| 南昌县| 高密市| 安达市| 丹东市| 长沙市| 滨海县| 台江县| 大宁县| 湄潭县| 南城县| 河源市| 河东区| 文化| 庐江县| 体育| 会东县| 峨边| 肥乡县| 普陀区| 通榆县| 寿阳县| 宁津县| 昌宁县| 道孚县| 绥宁县| 玉林市| 顺昌县| 海宁市| 陆河县| 渭源县|