专栏名称: 小猿猴GISer
GIS遥感交流学习
目录
相关文章推荐
雷科技  ·  “董明珠健康家”,真把我看呆了! ·  13 小时前  
国家林业和草原局  ·  新闻联播:我国古树名木保护取得积极进展 ·  3 天前  
国家林业和草原局  ·  新闻联播:我国古树名木保护取得积极进展 ·  3 天前  
51好读  ›  专栏  ›  小猿猴GISer

利用 Vue3 + Vite 搭建一个 Openlayers 一张图项目

小猿猴GISer  · 公众号  ·  · 2024-08-15 17:57

正文

创建项目

本文将使用 Vue3 + Vite 组合来搭建一个 Openlayers 项目,首先我们在命令行中输入如下命令来创建一个项目:细节请参考官网文档 https://cn.vuejs.org/guide/quick-start.html ,这里不再赘述。

npm create vue@latest

安装常用的开发包

安装运行时依赖 dependencies pinia vue-router 我们在创建项目时额外地进行了选择安装,没有安装的这里可以一并安装上。另外多个开发包可以一并安装,包名称之间以 空格 分开即可。

npm i @element-plus/icons-vue element-plus ol

安装开发时依赖 devDependencies :命令中多加一个 -D 则会将开发包安装至开发时依赖下。

npm i -D @iconify-json/ep @vitejs/plugin-vue sass sass-loader nplugin-auto-import nplugin-icons unplugin-vue-components

编辑器格式化配置

新建一个名为 .editorconfig 文件:

# http://editorconfig.org
root = true

[*]
#缩进风格:空格
indent_style = space
#缩进大小2
indent_size = 2
#换行符lf
end_of_line = lf
#字符集utf-8
charset = utf-8
#是否删除行尾的空格
trim_trailing_whitespace = true
#是否在文件的最后插入一个空行
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

[Makefile]
indent_style = tab

新建一个名为 .prettierrc 文件:这里我们格式化的插件最好配合 Prettier

{
  "htmlWhitespaceSensitivity""ignore",
  "bracketSameLine"true //避免结束标签的右尖括号换行
}

包自动导入环境配置

上述包自动导入相关开发包已安装完毕,这里我们需要在 vite.config.js 文件中来配置一下,以实现 Element 及其 Icon 的自动导入,这里直接将所有的配置选项贴出来。

import { fileURLToPath, URL } from "node:url";

import { defineConfig } from "vite";
import vue from  "@vitejs/plugin-vue";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
import Icons from "unplugin-icons/vite";
import IconsResolver from "unplugin-icons/resolver";

// https://vitejs.dev/config/
export default defineConfig({
  transpileDependenciestrue,
  lintOnSavefalse//关闭eslint检测
  server: {
    hosttrue,
    port8088,
    opentrue,
  },
  plugins: [
    vue(),
    AutoImport({
      imports: ["vue""pinia""vue-router"],
      resolvers: [
        // Element按需加载
        ElementPlusResolver(),
        // 自动导入图标组件
        IconsResolver({
          prefix"Icon",
        }),
      ],
    }),
    Components({
      deeptrue// 搜索子目录
      dirs: ["src/components"], // 按需加载的文件夹
      resolvers: [
        // 自动注册图标组件
        IconsResolver({
          enabledCollections: ["ep"],
        }),
        // 自动导入 Element Plus 组件
        ElementPlusResolver(),
      ],
    }),
    Icons({
      autoInstalltrue,
    }),
  ],
  resolve: {
    alias: {
      "@": fileURLToPath(new URL("./src"import.meta.url)),
    },
  },
});

地图对象的全局共享

这里我们借助 Pinia 这开发包便可以实现全局共享,但有一点需要注意,就是避免 Map 对象的响应式劫持,又由于Vue3更推荐使用 ref 而不是 reactive ,这里我们可以借助 shallowRef 来实现,相比于 ref shallowRef 仅对 .value 的访问为响应式的,并不会被深层递归。

更多关于 shallowRef 的讲解,官方文档解释的很清楚,这里贴一下地址: https://cn.vuejs.org/api/reactivity-advanced.html

由于之前关于引入天地图的方法我们已经有探讨过,这里直接将地图组件代码贴出来:引入天地图后,将创建的 浅响应式的Map对象 存储到 Pinia 中。

<script setup>
import Map from "ol/Map.js";
import View from "ol/View.js";
import { defaults as defaultInteractions } from "ol/interaction.js";
import { unByKey } from "ol/Observable.js";

import Tianditu from "@/utils/tdt.js";
import useOlMapStore from "@/stores/olMap.js";
import { onMounted } from "vue";

let mapDivRef = ref();
let mapRef = shallowRef();
let mapClickEvtKey = null;
const emits = defineEmits(["mapClick"]);

const props = defineProps({
  defaultTdtLyrType: {
    typeString,
    default"vec",
  },
});

const TDT = new Tianditu();
const vecLyrGrp = TDT.createTileLayerGroup(
  "vec",
  "vec" === props.defaultTdtLyrType
);

const imgLyrGrp = TDT.createTileLayerGroup(
  "img",
  "img" === props.defaultTdtLyrType
);

const view = new View({
  center: [113.5934.78],
  zoom10,
  maxZoom28,
  projection"EPSG:4326",
});

onMounted(() => {
  createMap();
  initMapEvt();
});

function createMap({
  mapRef.value = new Map({
    target: mapDivRef.value,
    layers : [vecLyrGrp, imgLyrGrp],
    view,
    controls: [],//为空数组时,则将默认的控件全部关闭
    interactions: defaultInteractions({ doubleClickZoomfalse }), //关闭双击交互
  });
  useOlMapStore().map = mapRef;
  console.log("child""onMounted",useOlMapStore().map);
}

function initMapEvt({
  mapClickEvtKey = mapRef.value.on("singleclick", (e) => {
    emits("mapClick", e);
  });
}

onBeforeUnmount(() => {
  unByKey(mapClickEvtKey);
});
script>

<template>
  <div class="map" ref="mapDivRef">div>
template>

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

我们将利用上述组件作为子组件在一个父组件( OneMap )中引入,并在父子组件的 onMounted 生命周期中输出一下 Pinia 管理的全局对象中存储的 Map 对象。

<script setup>
import useOlMapStore from "@/stores/olMap.js";

const mapStore = useOlMapStore();

onMounted(() => {
  console.log("parent""onMounted", mapStore.map);
});

function mapClick(evt{
  //TODO
}
script>

<template>
  <TdtMap @mapClick="mapClick" />
template>

运行项目后,查看控制台,如下图所示,容易看出:

  1. 子组件的 onMounted 会先执行,父组件的 onMounted 会后执行;
  2. 父子组件中输出的 Map 对象都非响应式。

看到这里,有人会问了,你怎么知道这个Map存储的为非响应式,显然,对象并没有被 Proxy 劫持,况且上述 shallowRef 的作用也说了,只是对 .value 做了响应式,并没有 深层递归 。这里我们将响应式的结果输出来看一下就明白了,修改一下代码:我们在子组件中往 Pinia 中存储 Map 对象时,将 mapRef.value 存储到Pinia中,输出结果如下所示:

useOlMapStore().map = mapRef.value;

好了,Map对象避免响应式劫持且全局共享的目标便实现了。

实现一个简单的属性弹窗

先来看一下实现效果:点击地图,弹窗展示当前点击的位置坐标:这里将坐标保留了 4 位有效数字。



我们创建一个·Popup·组件:这里我们用到了 Openlayers Overlay 以及 Element el-descriptions UI组件及其图标库。

<script setup>
import useOlMapStore from "@/stores/olMap.js";
import Overlay from "ol/Overlay";
import { onBeforeUnmount } from "vue";

const mapStore = useOlMapStore();

const props = defineProps({
  title: {
    typeString,
    default"标题",
  },
  position: {
    typeArray,
    default() => [],
  },
  offset: {
    typeArray,
    default() => [0-5],
  },
  autoPan: {
    typeBoolean,
    defaulttrue,
  },
  //属性相关
  propeties: {
    typeObject,
    default() => {
      return {};
    },
  },
  popupInfo: {
    typeObject,
    default() => {
      return {};
    },
  },
});

let popOverLay = null;
let popup = ref();
const emits = defineEmits(["close"]);

const getAutoPan = computed(() => {
  return props.autoPan
    ? {
        animation: {
          duration250,
        },
        margin30






请到「今天看啥」查看全文