前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >结合ace编辑器实现MapboxGL热力图样式在线配置

结合ace编辑器实现MapboxGL热力图样式在线配置

作者头像
lzugis
发布2023-07-11 13:53:50
2470
发布2023-07-11 13:53:50
举报

概述

MapboxGL热力图的配置参数并不多,但是有时候为了或得一个比较好用的热力图配置参数,我们不得不改代码再预览,显得尤为麻烦,为方便配置,实现实时预览,本文使用ace实现了一个热力图样式在线配置页面。

效果

image.png
image.png

实现

1. 技术栈

  • Vue3 + Element Plus
  • ace Editor
  • mapboxGL

2. 实现功能

  • csv、json、geojson数据上传并解析
  • mapboxGL热力图
  • 热力图样式编辑与实时预览

3. 实现

3.1 交互界面

代码语言:javascript
复制
<template>
  <div class="tips">
    <b>说明:</b>实现热力图样式的配置与预览。
  </div>
  <div class="container">
    <div class="setting-panel">
      <div class="title">配置参数</div>
      <div class="content">
        <el-form
          label-width="0"
          :model="styleFormData"
        >
          <el-form-item label="">
            <div class="label">
              Radius
              <div class="tooltip">
                <el-icon><InfoFilled /></el-icon>
                <div class="tooltips">
                  <p>Radius of influence of one heatmap point in pixels. Increasing the value makes the heatmap smoother, but less detailed.</p>
                </div>
              </div>
            </div>
            <el-input class="my-input" size="small" :rows="2" type="textarea" v-model="styleFormData.radius" />
          </el-form-item>
          <el-form-item label="">
            <div class="label">
              Color
              <div class="tooltip">
                <el-icon><InfoFilled /></el-icon>
                <div class="tooltips">
                  <p>Defines the color of each pixel based on its density value in a heatmap. Should be an expression that uses ["heatmap-density"] as input.</p>
                </div>
              </div>
            </div>
            <el-input class="my-input" size="small" :rows="4" type="textarea" v-model="styleFormData.color" />
          </el-form-item>
          <el-form-item label="">
            <div class="label">
              Weight
              <div class="tooltip">
                <el-icon><InfoFilled /></el-icon>
                <div class="tooltips">
                  <p>A measure of how much an individual point contributes to the heatmap. A value of 10 would be equivalent to having 10 points of weight 1 in the same spot. Especially useful when combined with clustering.</p>
                </div>
              </div>
            </div>
            <el-input class="my-input" size="small" :rows="2" type="textarea" v-model="styleFormData.weight" />
          </el-form-item>
          <el-form-item label="">
            <div class="label">
              Intensity
              <div class="tooltip">
                <el-icon><InfoFilled /></el-icon>
                <div class="tooltips">
                  <p>Similar to `heatmap-weight` but controls the intensity of the heatmap globally. Primarily used for adjusting the heatmap based on zoom level.</p>
                </div>
              </div>
            </div>
            <el-input class="my-input" size="small" :rows="2" type="textarea" v-model="styleFormData.intensity" />
          </el-form-item>
          <el-form-item label="">
            <div class="label">
              Opacity
              <div class="tooltip">
                <el-icon><InfoFilled /></el-icon>
                <div class="tooltips">
                  <p>The global opacity at which the heatmap layer will be drawn.</p>
                </div>
              </div>
            </div>
            <el-input class="my-input" size="small" :rows="2" type="textarea" v-model="styleFormData.opacity" />
          </el-form-item>
        </el-form>
      </div>
      <div class="title">
        JSON编辑器
        <div class="tools">
          <el-button size="small" @click="copyStyle">复制</el-button>
        </div>
      </div>
      <div class="content code" id="codeEditor"></div>
    </div>
    <div class="main-panel">
      <div class="data-panel">
        <el-upload
          drag
          ref="file"
          action="''"
          :multiple="false"
          :auto-upload="false"
          :limit="1"
          :on-exceed="handleExceed"
          :on-change="changeDataFile"
          :accept="'.csv,.json,.geojson'"
        >
          <div class="el-upload__text">
            拖动文件到此或 <em>点击上传</em>
          </div>
          <template #tip>
            <div class="el-upload__tip">
              可上传csv、json、geojson等格式点数据,如为csv、json需包含lon,lat字段,如添加<b style="color: red">权重</b>,需<b style="color: red">值</b>字段
            </div>
          </template>
        </el-upload>
      </div>
      <MapComponent :is-tools="false" @map-loaded="mapLoaded" style="height: 100%;"></MapComponent>
    </div>
  </div>
</template>

<style scoped lang="scss">
@import "../../assets/common/style";
.container {
  margin-top: 1.5rem;
  height: calc(100% - 3.5rem);
  position: relative;
  display: flex;
  flex-direction: row;
  .main-panel {
    flex-grow: 1;
    height: calc(100% - 1.8rem);
    position: relative;
    .data-panel {
      padding: 0.8rem;
      background-color: white;
      position: absolute;
      top: 1rem;
      right: 1rem;
      z-index: 99;
      width: 25rem;
    }
  }
  .setting-panel {
    width: 25rem;
    height: 100%;
    box-shadow: 0 0 5px #ccc;
    box-sizing: border-box;
    margin-right: 1.5rem;
  }
  .title {
    padding: 0.6rem 1.2rem;
    font-weight: bold;
    font-size: 1.1rem;
    border: 1px solid #efefef;
  }
  .content {
    padding: 1.2rem 1.2rem 0 1.2rem;
    &.code {
      height: calc(100% - 33.7rem)
    }
  }
  .tools {
    float: right;
  }
  .label, .my-input {
    display: inline-block;
    width: calc(100% - 7rem);
    .el-input__wrapper {
      width: 100%;
    }
  }
  .label {
    width: 6rem;
    height: 100%;
    line-height: 1.8;
    text-align: right;
    padding-right: 0.6rem;
  }

  .tooltip {
    display: inline-block;
    cursor: pointer;
    position: relative;
    &:hover {
      .tooltips {
        display: block;
      }
    }
    .tooltips {
      display: none;
      position: absolute;
      left: -8px;
      top: 22px;
      background-color: rgba(0,0,0,0.6);
      color: #fff;
      border-radius: 3px;
      z-index: 999;
      padding: 0.5rem;
      width: 17rem;
      white-space: normal;
      font-size: 12px;
      p {
        width: 100%;
        word-break: break-word;
        margin: 0;
        text-align: left;
        line-height: 1.5;
      }
      &:before {
        content: ' ';
        width: 0;
        height: 0;
        border: 5px solid transparent;
        border-bottom-color: rgba(0,0,0,0.6);
        position: absolute;
        left: 10px;
        top: -10px;
      }
    }
  }
}
</style>

3.2 数据上传与解析

代码语言:javascript
复制
changeDataFile(file, fileList) {
  uploadFile = file
  this.showData()
},
handleExceed(files) {
  this.$refs.file.clearFiles()
  this.$refs.file.handleStart(files[0])
},
showData() {
  const that = this
  if(!uploadFile) {
    ElMessage({
      message: '未上传文件!',
      type: 'warning',
    })
    return
  }
  const fileType = uploadFile.name.split('.')[1]
  const reader = new FileReader();
  reader.readAsText(uploadFile.raw,'GB2312');
  reader.onload = function () {
    const fileContent = reader.result;
    let geojson = null
    if(fileType === 'csv') {
      let {geomType, features} = csv2geojson(fileContent)
      geomType = geomType.toLowerCase()
      if (geomType.indexOf('point') !== -1) {
        geojson = new Geojson(features)
      }
    } else if(fileType === 'json') {
      let {geomType, features} = json2Geojson(JSON.parse(fileContent))
      geomType = geomType.toLowerCase()
      if (geomType.indexOf('point') !== -1) {
        geojson = new Geojson(features)
      }
    } else {
      geojson = JSON.parse(fileContent)
    }
    if(geojson) {
      map.getSource(`${DATA_LAYER}-source`).setData(geojson);
      that.styleUpdate()
      const [xmin, ymin, xmax, ymax] = turf.bbox(geojson);
      const bbox = [[xmin, ymin], [xmax, ymax]];
      map.fitBounds(bbox, {
        padding: {top: 100, bottom:100, left: 150, right: 150},
        duration: 500
      })
    }
  }
},

csv2geojson和json2Geojson转换方法如下:

代码语言:javascript
复制
import {Feature} from './geojson'
import { wktToGeoJSON } from "@terraformer/wkt"

export function csv2geojson(csvContent) {
  const splitChar = csvContent.indexOf('\r') ? '\r' : '\r\n'
  const lines = csvContent.split(splitChar).filter(v => Boolean(v))
  const headers = lines[0].split(',').map(header => header.toLowerCase())
  let geomType = '', features = [], isWkt = false
  if(headers.includes('lon') && headers.includes('lat')) {
    geomType = 'Point'
  } else if(headers.includes('wkt')) {
    isWkt = true
    const geom = wktToGeoJSON(lines[1].split(',')[headers.indexOf('wkt')])
    geomType = geom.type
  }
  if(geomType) {
    for (let i = 1; i < lines.length; i++) {
      const line = lines[i].split(',')
      if(line.length === headers.length) {
        let props = {}
        headers.forEach((header, index) => {
          if(!['wkt', 'lon', 'lat'].includes(header))  props[header] = line[index]
        })
        const lonIndex = headers.indexOf('lon')
        const latIndex = headers.indexOf('lat')
        const geometry = isWkt ?  wktToGeoJSON(line[headers.indexOf('wkt')]) : [line[lonIndex], line[latIndex]].map(Number)
        features.push(new Feature(geomType, props, geometry))
      }
    }
  }
  return {
    headers,
    geomType,
    features
  }
}

export function json2Geojson(json) {
  if(!Array.isArray(json)) throw new Error('数据格式错误')
  const geomType = 'Point'
  const features = json.map(d => {
    const {lon, lat} = d
    return new Feature(geomType, d, [lon, lat])
  })
  return {
    geomType,
    features
  }
}

3.3 样式编辑与实时预览

代码语言:javascript
复制
initEditor() {
  editor = ace.edit("codeEditor");
  const theme = "github";
  const language = "json";
  editor.setTheme("ace/theme/" + theme);
  editor.session.setMode("ace/mode/" + language);
  editor.setFontSize(14);
  editor.setReadOnly(false);
  editor.setOption("wrap", "free");
  editor.setOptions({
    enableBasicAutocompletion: true,
    enableSnippets: true,
    enableLiveAutocompletion: true,
    tabSize: 2
  });
  this.styleUpdate()
},
styleUpdate() {
  const style = {
    "heatmap-radius": this.styleFormData.radius,
    "heatmap-color": this.styleFormData.color,
    "heatmap-weight": this.styleFormData.weight,
    "heatmap-intensity": this.styleFormData.intensity,
    "heatmap-opacity": this.styleFormData.opacity,
  }
  let isOk = true
  for (const styleKey in style) {
    let val = style[styleKey]
    if(typeof val === 'string') val = val.replace(/'/g, '"')
    if(val === '') isOk = false
    if(styleKey !== 'heatmap-color' && ! Number.isNaN(Number(val))) style[styleKey] = Number(va
    else style[styleKey] = JSON.parse(val || '{}')
    if(styleKey === 'heatmap-opacity' && style[styleKey] > 1) style[styleKey] = 1
    if(styleKey === 'heatmap-opacity' && style[styleKey] < 0) style[styleKey] = 0
  }
  if(isOk) {
    editor.setValue(JSON.stringify(style, null, 2))
    if(window.map) {
      if(map.getLayer(`${DATA_LAYER}-layer`)) map.removeLayer(`${DATA_LAYER}-layer`)
      map.addLayer({
        id: `${DATA_LAYER}-layer`,
        type: "heatmap",
        source: `${DATA_LAYER}-source`,
        paint: style
      });
    }
  }
},
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-06-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
  • 效果
  • 实现
    • 1. 技术栈
      • 2. 实现功能
        • 3. 实现
          • 3.1 交互界面
          • 3.2 数据上传与解析
          • 3.3 样式编辑与实时预览
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档