首页 > 编程语言 >微信小程序地图Map结合canvas实现手动绘制地图区域

微信小程序地图Map结合canvas实现手动绘制地图区域

时间:2024-07-26 18:53:40浏览次数:17  
标签:Map canvas 绘制地图 number longitude screenHeight latitude const wx

1. 功能概述

在微信小程序中,用户手动在地图上绘制区域,将绘制的区域边界点转换为经纬度在地图上显示绘制的区域。此功能实现了用户与地图的交互,可以应用于地理围栏、区域标记等场景。

2. 实现步骤

2.1 获取用户位置

在小程序加载时,使用 wx.getLocation 获取用户的当前位置,并设置地图的初始经纬度。

Page({
  data: {
    longitude: 0,
    latitude: 0,
    polygons: [] as Array<{
      points: Array<{ longitude: number; latitude: number }>;
    }>,
    drawing: false,
    canvasPoints: [] as Array<{ x: number; y: number }>,
    screenWidth: 0,
    screenHeight: 0,
    mapScale: 14,
  },

  onl oad() {
    const that = this;
    wx.getLocation({
      type: "gcj02",
      success(res) {
        that.setData({
          longitude: res.longitude,
          latitude: res.latitude,
        });
      },
    });

    const systemInfo = wx.getSystemInfoSync();
    this.setData({
      screenWidth: systemInfo.windowWidth,
      screenHeight: systemInfo.windowHeight,
    });

    wx.startLocationUpdate({
      success() {
        wx.onLocationChange((location) => {
          if (!that.data.drawing) {
            const { longitude, latitude } = location;
            that.setData({ longitude, latitude });
          }
        });
      },
    });
  },

2.2 用户绘制区域

通过在地图上覆盖一层透明的Canvas,用户可以在Canvas上绘制区域。在绘制过程中,记录触摸点的坐标。

  handleCanvasTouchStart() {
    this.setData({ drawing: true, canvasPoints: [] });
  },

  handleCanvasTouchMove(event: any) {
    if (this.data.drawing) {
      const { x, y } = event.touches[0];
      const { canvasPoints } = this.data;
      canvasPoints.push({ x, y });
      this.setData({ canvasPoints });
      this.redrawCanvas();
    }
  },

  handleCanvasTouchEnd() {
    this.setData({ drawing: false });
    this.convertCanvasToLatLng();
  },

  redrawCanvas() {
    const ctx = wx.createCanvasContext("drawCanvas", this);
    const points = this.data.canvasPoints;
    ctx.setFillStyle("rgba(255, 0, 0, 0.3)");
    ctx.beginPath();
    points.forEach((point, index) => {
      if (index === 0) {
        ctx.moveTo(point.x, point.y);
      } else {
        ctx.lineTo(point.x, point.y);
      }
    });
    ctx.closePath();
    ctx.fill();
    ctx.draw();
  },

2.3 将Canvas坐标转换为经纬度

在触摸结束后,将Canvas上的点坐标转换为地图上的经纬度,并将转换后的点添加到地图的 polygon 中。

  convertCanvasToLatLng() {
    const {
      screenWidth,
      screenHeight,
      longitude,
      latitude,
      mapScale,
    } = this.data;
    const mapCtx = wx.createMapContext("map", this);

    const points = this.data.canvasPoints.map((point) => {
      return new Promise<{ longitude: number; latitude: number }>((resolve) => {
        mapCtx.getScale({
          success: (scaleRes) => {
            const mapScale = scaleRes.scale;
            const numberMapper = new NumberMapper();
            const mappedValue = numberMapper.getMappedValue(mapScale);
            mapCtx.getRegion({
              success(region) {
                const lngDelta =
                  region.northeast.longitude - region.southwest.longitude;
                const latDelta =
                  region.northeast.latitude - region.southwest.latitude;
                const lng =
                  longitude +
                  ((point.x - screenWidth / 2) * lngDelta) / screenWidth;
                const lat =
                  latitude -
                  ((point.y - screenHeight / 2) * latDelta) / screenHeight +
                  mappedValue;
                resolve({ longitude: lng, latitude: lat });
              },
            });
          },
        });
      });
    });

    Promise.all(points).then((latLngPoints) => {
      this.setData({
        polygons: [{ points: latLngPoints }],
      });
    });
  },

2.4 清空画布

在绘制结束并转换坐标后,清空画布以准备下一次绘制。

  convertCanvasToLatLng() {
    const { screenWidth, screenHeight, longitude, latitude } = this.data;
    const mapCtx = wx.createMapContext("map", this);

    const points = this.data.canvasPoints.map((point) => {
      return new Promise<{ longitude: number; latitude: number }>((resolve) => {
        mapCtx.getRegion({
          success(region) {
            const lngDelta =
              region.northeast.longitude - region.southwest.longitude;
            const latDelta =
              region.northeast.latitude - region.southwest.latitude;
            const lng =
              longitude +
              ((point.x - screenWidth / 2) * lngDelta) / screenWidth;
            const lat =
              latitude -
              ((point.y - screenHeight / 2) * latDelta) / screenHeight;
            resolve({ longitude: lng, latitude: lat });
          },
        });
      });
    });

    Promise.all(points).then((latLngPoints) => {
      this.setData({
        polygons: [{ points: latLngPoints }],
        canvasPoints: [] // 清空画布点
      });
    });
  },

3. 减小偏移的解决方案

3.1 基于地图缩放值(scale)的修正

在 convertCanvasToLatLng 方法中,使用 mapCtx.getScale 获取当前地图的缩放值,通过自定义的NumberMapper模块对缩放值进行修正,使得不同缩放级别下的偏移量尽可能减小。

class NumberMapper {
  private hashMap: { [key: number]: number } = {
    3: 12.0,
    3.5: 11.0,
    4: 9.7,
    4.5: 6.5,
    5: 4.8,
    5.5: 3,
    6: 2.33,
    6.5: 1.54,
    7: 1.07,
    7.5: 0.76,
    8: 0.445,
    8.5: 0.375,
    9: 0.265,
    9.5: 0.193,
    10: 0.125,
    10.5: 0.102,
    11: 0.062,
    11.5: 0.048,
    12: 0.0316,
    12.5: 0.0241,
    13: 0.0161,
    13.5: 0.011,
    14: 0.00801,
    14.5: 0.0055,
    15: 0.00401,
    15.5: 0.00271,
    16: 0.00195,
    16.5: 0.00138,
    17: 0.001,
    17.5: 0.0007,
    18: 0.0005,
    18.5: 0.00035,
    19: 0.00024,
    19.5: 0.000171,
    20: 0.000121,
  };

  private processNumber(num: number): number {
    const integerPart = Math.floor(num);
    const decimalPart = num - integerPart;
    if (decimalPart === 0) {
      return integerPart;
    } else if (decimalPart > 0 && decimalPart < 0.5) {
      return integerPart + 0.5;
    } else {
      return integerPart + 1;
    }
  }

  public getMappedValue(num: number): number {
    if (num < 3 || num > 20) {
      throw new Error("Number out of range. Must be between 3 and 20.");
    }
    const ratio = getScreenRatio();
    const processedNumber = this.processNumber(num);
    return this.hashMap[processedNumber];
  }
}

const getScreenRatio = (): number => {
  const systemInfo = wx.getSystemInfoSync();
  const screenWidth = systemInfo.windowWidth;
  const screenHeight = systemInfo.windowHeight;
  const aspectRatio = (screenWidth / screenHeight).toFixed(4);
  return parseFloat(aspectRatio);
};

export default getScreenRatio;

3.2 基于不同机型屏幕宽高比的修正

通过 wx.getSystemInfoSync 获取不同机型的屏幕宽高比,创建一个哈希映射表,将不同宽高比映射到对应的修正值。这样可以减小由于不同设备屏幕比例差异带来的偏移问题。

type RatioMap = { [key: string]: number };
const ratioMap: RatioMap = {
  0.4824: 0.0072,
  // 继续添加其他比例和对应的修正值
};
const getScreenRatio = (): number => {
  const systemInfo = wx.getSystemInfoSync();
  const screenWidth = systemInfo.windowWidth;
  const screenHeight = systemInfo.windowHeight;
  const aspectRatio = (screenWidth / screenHeight).toFixed(4);
  return getMappedValue(parseFloat(aspectRatio));
};
const getMappedValue = (ratio: number): number => {
  return ratioMap[ratio] || 0; // 如果找不到对应值,返回0或其他默认值
};

export default getScreenRatio;

4.完整的代码

确保文件目录结构如下

logs/
├── module/
│   ├── numberMapper.ts
│   ├── screenRatio.ts
├── logs.json
├── logs.scss
├── logs.ts
└── logs.wxml

4.1 wxml

<view class="container">
    <map id="map" class="map" enable-scroll="{{false}}" longitude="{{longitude}}" latitude="{{latitude}}" scale="14" show-location polygons="{{polygons}}">
    </map>
    <canvas style="position:absolute;top:0;left:0;width:100%;height:100%;" canvas-id="drawCanvas" bindtouchstart="handleCanvasTouchStart" bindtouchmove="handleCanvasTouchMove" bindtouchend="handleCanvasTouchEnd"></canvas>
</view>

4.2 scss

.container {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  position: relative;
}
.map {
  width: 100%;
  height: 100vh;
}

4.3 logs.ts

import NumberMapper from "./module/numberMapper";
Page({
  data: {
    longitude: 0,
    latitude: 0,
    polygons: [] as Array<{
      points: Array<{ longitude: number; latitude: number }>;
      strokeWidth: Number;
      strokeColor: string;
      fillColor: string;
    }>,
    drawing: false,
    canvasPoints: [] as Array<{ x: number; y: number }>,
    screenWidth: 0,
    screenHeight: 0,
    mapScale: 14,
  },

  onl oad() {
    const that = this;
    wx.getLocation({
      type: "gcj02",
      success(res) {
        that.setData({
          longitude: res.longitude,
          latitude: res.latitude,
        });
      },
    });

    const systemInfo = wx.getSystemInfoSync();
    this.setData({
      screenWidth: systemInfo.windowWidth,
      screenHeight: systemInfo.windowHeight,
    });

    wx.startLocationUpdate({
      success() {
        wx.onLocationChange((location) => {
          if (!that.data.drawing) {
            const { longitude, latitude } = location;
            that.setData({ longitude, latitude });
          }
        });
      },
    });
  },

  handleCanvasTouchStart() {
    this.setData({ drawing: true, canvasPoints: [] });
  },

  handleCanvasTouchMove(event: any) {
    if (this.data.drawing) {
      const { x, y } = event.touches[0];
      const { canvasPoints } = this.data;
      canvasPoints.push({ x, y });
      this.setData({ canvasPoints });
      this.redrawCanvas();
    }
  },

  handleCanvasTouchEnd() {
    // this.setData({ drawing: false });
    // this.convertCanvasToLatLng();
    this.setData({ drawing: false });
    this.convertCanvasToLatLng().then(() => {
      this.clearCanvas();
    });
  },
  clearCanvas() {
    const ctx = wx.createCanvasContext("drawCanvas", this);
    ctx.clearRect(0, 0, this.data.screenWidth, this.data.screenHeight);
    ctx.draw();
  },

  redrawCanvas() {
    const ctx = wx.createCanvasContext("drawCanvas", this);
    const points = this.data.canvasPoints;
    ctx.setFillStyle("rgba(222, 160, 84, 0.3)");
    ctx.beginPath();
    points.forEach((point, index) => {
      if (index === 0) {
        ctx.moveTo(point.x, point.y);
      } else {
        ctx.lineTo(point.x, point.y);
      }
    });
    ctx.closePath();
    ctx.fill();
    ctx.draw();
  },

  convertCanvasToLatLng() {
    const {
      screenWidth,
      screenHeight,
      longitude,
      latitude,
      mapScale,
    } = this.data;
    const mapCtx = wx.createMapContext("map", this);
    return new Promise<void>((resolve) => {
      const points = this.data.canvasPoints.map((point) => {
        return new Promise<{ longitude: number; latitude: number }>(
          (resolve) => {
            mapCtx.getScale({
              success: (scaleRes) => {
                const mapScale = scaleRes.scale;
                const numberMapper = new NumberMapper();
                const mappedValue = numberMapper.getMappedValue(mapScale);
                console.log(mappedValue, "mappedValue");
                mapCtx.getRegion({
                  success(region) {
                    const lngDelta =
                      region.northeast.longitude - region.southwest.longitude;
                    const latDelta =
                      region.northeast.latitude - region.southwest.latitude;
                    const lng =
                      longitude +
                      ((point.x - screenWidth / 2) * lngDelta) / screenWidth;
                    const lat =
                      latitude -
                      ((point.y - screenHeight / 2) * latDelta) / screenHeight +
                      mappedValue;
                    resolve({ longitude: lng, latitude: lat });
                  },
                });
              },
            });
          }
        );
      });

      Promise.all(points).then((latLngPoints) => {
        this.setData({
          polygons: [
            {
              points: latLngPoints,
              strokeWidth: 3,
              strokeColor: "#dea054",
              fillColor: "#dea0544D",
            },
          ],
        });
        resolve();
      });
    });
  },
});

4.4 module/numberMapper.ts

type HashMap = { [key: number]: number };
import getScreenRatio from "./screenRatio";
class NumberMapper {
  private hashMap: HashMap;

  constructor() {
    this.hashMap = {
      3: 12.0,
      3.5: 11.0,
      4: 9.7,
      4.5: 6.5,
      5: 4.8,
      5.5: 3,
      6: 2.33,
      6.5: 1.54,
      7: 1.07,
      7.5: 0.76,
      8: 0.445,
      8.5: 0.375,
      9: 0.265,
      9.5: 0.193,
      10: 0.125,
      10.5: 0.102,
      11: 0.062,
      11.5: 0.048,
      12: 0.0316,
      12.5: 0.0241,
      13: 0.0161,
      13.5: 0.011,
      14: 0.00801,
      14.5: 0.0055,
      15: 0.00401,
      15.5: 0.00271,
      16: 0.00195,
      16.5: 0.00138,
      17: 0.001,
      17.5: 0.0007,
      18: 0.0005,
      18.5: 0.00035,
      19: 0.00024,
      19.5: 0.000171,
      20: 0.000121,
    };
  }

  private processNumber(num: number): number {
    const integerPart = Math.floor(num);
    const decimalPart = num - integerPart;
    if (decimalPart === 0) {
      return integerPart;
    } else if (decimalPart > 0 && decimalPart < 0.5) {
      return integerPart + 0.5;
    } else {
      return integerPart + 1;
    }
  }

  public getMappedValue(num: number): number {
    if (num < 3 || num > 20) {
      throw new Error("Number out of range. Must be between 3 and 20.");
    }
    const ratio = getScreenRatio(); 
    console.log(ratio, "ratioratio");
    const processedNumber = this.processNumber(num);
    return this.hashMap[processedNumber] - ratio;
  }
}

export default NumberMapper;

4.5 module/screenRatio.ts

type RatioMap = { [key: string]: number };
const ratioMap: RatioMap = {
  0.4824: 0.0072,
  // 添加更多映射值
};
const getScreenRatio = (): number => {
  const systemInfo = wx.getSystemInfoSync();
  const screenWidth = systemInfo.windowWidth;
  const screenHeight = systemInfo.windowHeight;
  const aspectRatio = (screenWidth / screenHeight).toFixed(4);
  return getMappedValue(parseFloat(aspectRatio));
};
const getMappedValue = (ratio: number): number => {
  return ratioMap[ratio] || 0; // 如果找不到对应值,返回0或其他默认值
};

export default getScreenRatio;

例如: module/screenRatio.ts

  1. 搭建 Java 开发环境
  2. 掌握 Java 基本语法
  3. 掌握条件语句
  4. 掌握循环语句

4.6 app.json

"requiredPrivateInfos": ["getLocation", "onLocationChange", "startLocationUpdate"],
"permission": {
        "scope.userLocation": {
            "desc": "你的位置信息将用于小程序位置接口的效果展示"
        }
 },

5.实现原理

•	获取用户位置:使用 wx.getLocation 和 wx.onLocationChange 获取并更新用户当前位置。
•	绘制区域:在地图上覆盖一层透明的 Canvas,用户通过触摸在 Canvas 上绘制区域,记录绘制的点坐标。
•	坐标转换:在触摸结束后,使用 wx.createMapContext 获取当前地图的缩放比例和区域信息,将 Canvas 上的点坐标转换为地图上的经纬度。
•	减少偏移:通过结合地图缩放比例和设备屏幕宽高比,对转换结果进行修正,以减少绘制区域和地图显示区域之间的偏移。
•	画布清空:在坐标转换完成后,清空画布上的点,准备下一次绘制。

6.总结

通过以上实现,成功解决了在微信小程序中用户手动绘制区域并将其显示在地图上的需求。主要步骤包括获取用户位置、绘制区域、进行坐标转换以及减少偏移。这一过程中的关键点在于结合了 map 与 canvas 的特性,实现了用户在地图上绘制区域的功能。

标签:Map,canvas,绘制地图,number,longitude,screenHeight,latitude,const,wx
From: https://blog.csdn.net/qq_40791475/article/details/140722323

相关文章

  • asyncio Queue和Semaphore的结合使用
    importasyncio#假设这是你的大数据集large_data_set=range(1000000)#用1到1000000的数字模拟大数据集#任务队列task_queue=asyncio.Queue()#并发限制sem=asyncio.Semaphore(10)#任务处理函数asyncdefprocess_data(sem,q):whileTrue:#......
  • MapperStruct 嵌套模型中 List<> 转 List<String>
    废话不多说,上代码 宗旨:将List<A>映射为List<String>一,实体类Source//Source中有一个List<A>publicclassSource{privateStringid;privateStringfrom;privateList<A>to;}//A对象中有个BpublicclassA{privateBb;}//B有个addre......
  • PixiJS源码分析系列:第三章 使用 canvas 作为渲染器
    使用canvasRenderer渲染上一章分析了一下Sprite在默认webgl渲染器上的渲染,这章让我们把目光聚集到canvasRenderer上使用canvas渲染器渲染图片的demo要使用canvas作为渲染器,我们需要引用pixi-legacy.js/bundles/pixi.js-legacy/dist/pixi-legacy.js像下面这样......
  • Android开发 - Canvas类与Paint画笔的绘制详解与使用
    Canvas类是什么Android中Canvas类常用于自定义View等操作中,Canvas则如同一张画布可以在上面绘制内容,然后这张画布也可以叠加其他的图层或者平移旋转等操作。Canvas对象的获取方式有两种:一种我们通过重写onDraw方法,View中重写onDraw(Canvascanvas)Canvas对象会被当做参数传递过......
  • Android开发 - Canvas中Path路径的详解与使用
    Path回顾Path类封装复合(多轮廓)几何路径由直线段、二次曲线和三次曲线组成。它可以用画布绘制:canvas.drawPath(path,paint),填充或笔划(基于绘画的样式),或者可以用于剪裁或绘制路径上的文本。Path既是路径,路径走多了就变成一种套路,只要我们会解套,那这种套路就是高速公路。路径走完形......
  • Java筛选数据:List的contains和Map的get哪个快?
    在Java中,List的contains方法和Map的get方法在性能上有一些区别,主要取决于数据结构的特性和使用场景:List的contains方法:List是一个有序集合,使用线性查找来确定列表中是否包含某个元素。时间复杂度为O(n),其中n是列表的大小。对于小型的List或者在列表中的......
  • C++| STL之unordered_map(哈希表)和map
    前言:Leetcode题目中有一个哈希表的专题,自己实现的话没必要,可以直接用STL现成的unordered_map函数,提到unordered_map就不得不提到map,于是有了此篇相关知识点的汇总。unordered_map和mapkey和valueunordered_map使用map原理对比unordered_map使用对比unordered_mapke......
  • 手写Semaphore信号量
    publicclassMySemaphore{privateSyncsync;publicMySemaphore(intcount){sync=newSync(count);}publicvoidacquire(){sync.acquireShared(1);}publicvoidrelease(){sync.releaseShared(1);......
  • 如何使用DataFrameMapper删除特定列中具有空值的行?
    我正在使用sklearn-pandas.DataFrameMapper来预处理我的数据。我不想输入特定列。如果此列是Null,我只想删除该行。有没有办法做到这一点?虽然DataFrameMapper没有内置方法来删除具有空值的行,但你可以通过在DataFrameMapper管道之前使用P......
  • std的map或者set中,比较浮点类型二维三维数据
    在map和set中,如果比较对象是二维或者三维数据,需要把二维三维数据的浮点数转换为比较精度。如果比较精度是0.001,那么数据的精度也必须是0.001,不然会出现如下情况:比较函数 structPoint001Comp{booloperator()(constPoint*l,constPoint*r)const{i......