Home

Awesome

<p align="center"> <img src="//mars3d.cn/logo.png" width="300px" /> </p> <p align="center">基于React技术栈的 Mars3D🌎基础项目系统</p> <p align="center"> <a target="_black" href="https://www.npmjs.com/package/mars3d"> <img alt="Npm version" src="https://img.shields.io/npm/v/mars3d.svg?style=flat&logo=npm&label=版本号" /> </a> <a target="_black" href="https://www.npmjs.com/package/mars3d"> <img alt="Npm downloads" src="https://img.shields.io/npm/dt/mars3d?style=flat&logo=npm&label=下载量" /> </a> <a target="_black" href="https://github.com/marsgis/mars3d"> <img alt="GitHub stars" src="https://img.shields.io/github/stars/marsgis/mars3d?style=flat&logo=github" /> </a> <a target="_black" href="https://gitee.com/marsgis/mars3d"> <img src="https://gitee.com/marsgis/mars3d/badge/star.svg?theme=dark" alt="star" /> </a> </p> ## 项目介绍

Mars3D 基础项目 是基于Mars3D 平台做的一个应用系统,提供的一个基础项目模版,包含常用基础地图功能,可在该基础项目上快速开发搭建新项目。方便快速搭建三维地图产品,敏捷开发,可复用,支持各种配置,适合各种场景使用。

项目特性

如果您不熟悉 React,也可以阅读:基础项目原生 JS 版基础项目 Vue 版

视频讲解

建议先看一遍视频讲解,再实际操作。您可以新页面查看高清视频

下载运行项目

下载代码

git clone https://github.com/marsgis/mars3d-react-project.git
git clone https://gitee.com/marsgis/mars3d-react-project.git

运行环境

// setting.json相关配置
{
  "eslint.format.enable": true,
  "[json]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[html]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[react]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  }
}

运行命令

请将机器 Node 版本升级到 v14 及以上版本

首次运行前安装依赖

npm install

//或使用代理
npm i --registry=http://registry.taobao.org

启动开发环境

npm run serve

编译构建

npm run build

运行效果

访问基础项目 React 版 在线体验效果和功能

image

浏览器支持

推荐使用Chrome 90+ 浏览器, 建议升级浏览器到最新版本访问。

IEEdgeFirefoxChromeSafari
not supportlast 2 versionslast 2 versionslast 2 versionslast 2 versions

如何反馈问题?

提交方式:

项目架构

技术选型

主要目录说明

mars3d-react-project
└───src                 主要项目代码
│   └───common          公共核心文件
│   └───components      公共组件
│   └───directive       指令
│   └───misc            ts模块定义
│   └───pages           页面入口
│   └───utils           工具方法
│   └───widget          功能相关的widget控件【重要】
│
└───public              静态资源
│   └───config          地图的配置文件等
│   └───img             图片资源
│
│─── .eslintrc.js        eslint配置文件
│─── package.json        项目配置信息
└─── vite.config.ts      vite 配置文件
└─── tsconfig.js         ts 配置文件
└─── *.html              各页面入口

功能主目录

项目所有功能主要在 src/widgets/*/*目录下,每一个功能对应了叶子目录下的一个index.tsxmap.ts 文件,复杂的 widget 目录下也会有相关子组件 xxx.tsx

react 下的 widget 设计,沿用了我们 原生 JS 版基础项目的设计理念:

widget 配置参数

widget 加载相关的代码在 src/common/store/widget.ts下,使用的 redux 管理相关状态,默认状态字段有

// 为 store state 声明类型
export interface DefaultOption {
  autoDisable?: boolean
  disableOther?: boolean | string[]
  group?: string // group相同的widget一定是互斥的
  meta?: any // 额外参数 不会在每次关闭后清除
}

export interface Widget {
  name: string // 唯一标识
  key?: string // 作为react diff 环节的key,用于控制组件重载
  component?: any // widget关联的异步组件
  autoDisable?: boolean // 是否能够被自动关闭
  disableOther?: boolean | string[] // 是否自动关闭其他widget,或通过数组指定需要被关闭的widget
  group?: string // group相同的widget一定是互斥的
  visible?: boolean // 显示隐藏
  data?: any // 额外传参 会在每次关闭后清除
  meta?: any // 额外参数 不会在每次关闭后清除
}

export interface WidgetState {
  widgets: Widget[] // widget具体配置
  openAtStart: string[] // 默认加载的widget
  defaultOption?: DefaultOption // 支持配置默认参数
}

widget 构流程图

示例的内部构造处理流程图:

image

如何增加新的 widget

下面我们以 src/widgets/demo/sample-dialog/ 为示例做讲解

1.创建示例

在 widgets 目录下按项目需要建立好多层目录,比如我们将测试和演示的 widget 放在src/widgets/demo目录下面,基础项目的功能放在src/widgets/basic目录下。

首先建立后 sample-dialog 目录,并参考已有示例新建index.tsxmap.ts 2 个文件。

index.tsx

index.tsx 中用来编写 react 组件的代码,这个文件必须通过 export default 的方式导出一个 react 组件,这个组件可以使 class 组件,也可以是函数组件

// 函数组件
import * as mapWork from "./map"
import { useLifecycle } from "@mars/common/uses/useLifecycle"
import { MarsDialog } from "@mars/components/MarsUI"
import { Space } from "antd"
import { useRef } from "react"

export default function (props) {
  useLifecycle(mapWork)

  const guiRef = useRef<any>()

  return (
    <MarsDialog title="函数组件" right={10} top={50} width={300} {...props}>
      我是函数组件
    </MarsDialog>
  )
}
// class组件
import { withLifeCyle } from "@mars/common/uses/useLifecycle"
import { MarsDialog } from "@mars/components/MarsUI"
import { Component } from "react"
import * as mapWork from "./main"

class Test extends Component<any, any> {
  constructor(props) {
    super(props)
    this.state = {
      test: ""
    }
  }

  render() {
    return (
      <MarsDialog title="class组件" right={10} top={50} width={300} {...props}>
        我是class组件
      </MarsDialog>
    )
  }
}

export default withLifeCyle(Test, mapWork)
配置 widget 的 props 参数

widget 的 prop 参数默认有四中配置方式,默认情况下优先级从低到高分别为 内联 prop、defaultOption 中的 props、meta 中的 props 配置,动态传参,使用方式如下

// 内联
<MarsDialog title="图上量算" width="300" height="530" top="50"></MarsDialog>
// defaultOption中 注意 必须是meta
const store: StoreOptions<State> = {
  state: {
    defaultOption: {
      meta: {
        props: {
          top: 110
        }
      }
    },
    widgets: []
  }
}
// meta中
widgets = [
  {
    component: lazy(() => import("@mars/widgets/hello.tsx")),
    name: "hello",
    autoDisable: true,
    meta: {
      props: {
        top: 70
      }
    }
  }
]
// 动态传参
activate({
  name: "hello",
  data: {
    props: {
      top: 70
    }
  }
})
map.ts

react 中需要调用地图方法时,需得启用 map.ts 的生命周期,并且在 map.ts 生命周期中获取 map 对象。 但是函数组件和 class 组件的处理方式有所不同

注意:

  1. 开启生命周期的操作只需要在 index.tsx 中执行,子组件不需要。

map.ts

map.ts 完整代码为:

import * as mars3d from "mars3d"

export let map: mars3d.Map // 地图对象

// 事件对象,用于抛出事件给react
export const eventTarget = new mars3d.BaseClass()

// 初始化当前业务
export function onMounted(mapInstance: mars3d.Map): void {
  map = mapInstance // 记录map
}

// 释放当前业务
export function onUnmounted(): void {
  map = null
}

// 绘制矩形(演示map.js与index.react的交互)
export function drawExtent(): void {
  map.graphicLayer.clear()
  // 绘制矩形
  map.graphicLayer.startDraw({
    type: "rectangle",
    style: {
      fill: true,
      color: "rgba(255,255,0,0.2)",
      outline: true,
      outlineWidth: 2,
      outlineColor: "rgba(255,255,0,1)"
    },
    success: function (graphic: mars3d.graphic.RectangleEntity) {
      const rectangle = graphic.getRectangle({ isFormat: true })
      eventTarget.fire("drawExtent", { extent: JSON.stringify(rectangle) }) // 抛出事件,可以react中去监听事件
    }
  })
}

其中:

onMounted

初始化当前地图业务的钩子方法,可以通过 onMounted 函数的获取到 map 主对象。

export function onMounted(mapInstance: mars3d.Map): void {
  map = mapInstance // 记录map 初始化当前业务
}

如果未调用,请请参考之前的步骤,检查是否正确的启用生命周期

onUnmounted

释放当前地图业务的钩子方法, 一般在 onMounted 添加的图层、绑定的事件,在 onUnmounted 中都需要做相反的移除、解绑等操作。

export function onUnmounted(): void {
  map = null // 释放当前业务
}

map.tsindex.tsx各自代码业务分离的原则

index.tsx 与 map.ts 交互

  1. index.tsx 直接调用 map.ts 中 导出的函数或对象
  2. index.tsx 调用 map.ts 中的函数拿到返回值,继续后续处理,异步操作返回值可以是 Promise
  3. map.ts 主动触发 ui 变化,通过 mars3d.BaseClass 事件中心处理。如
// map.ts
export const eventTarget = new mars3d.BaseClass()

function change() {
  mapWork.eventTarget.fire("change", { value: "hello change" })
}

// index.tsx
// 监听事件
mapWork.eventTarget.on("change", change)

// 组件卸载时
mapWork.eventTarget.off("change", change) // 注意:请及时销毁事件绑定

2.相关页面加入菜单入口

store.ts 清单配置

在对应 page 页面下的 src/pages/demo/widget-store.ts 中,需要配置刚才新建的 widget 相关信息;

import type { WidgetState } from "@mars/common/store/widget"
import { lazy } from "react"

const store: StoreOptions<WidgetState> = {
  state: {
    //已忽略其他配置
    widgets: [
      {
        component: lazy(() => import("@mars/widgets/demo/sample-dialog/index.tsx")),
        name: "sample-dialog"
      }
    ]
  }
}
export default store

其中 state 下的配置参数参考 widget 配置参数

更多参数建议阅读源码的 src/common/store/widget.ts (教程可能滞后,请参考源码注释为准)

菜单或其他入口文件中

在需要的菜单单击事件或其他对象触发代码中,加入activate('sample-dialog')来激活我们刚加入的控件,

下面已目录为例:

widgets/demo/menu/index.tsx中加入“弹窗示例”按钮,按钮单击事件调用对应方法,

activate 和 disable 函数支持 string(直接传递 name) 和 Widget(传递 widget 对象,将会合并传递的属性,必须包含 name 字段) 类型的参数,上述 name 字段与 store.ts 中的 name 需要一致。

将当前项目集成到自己的项目中(合并 2 个项目)

前提条件:需要 2 个项目的技术栈基本是一致的,比如react + ts+ant-design

流程概览:

此处演示的是vite技术栈下的操作

需要拷贝的目录和文件:

需要修改自己项目的文件:

1. 拷贝基础项目 src 代码

在原有项目中新建目录src/marsgis,将基础项目 src 代码拷贝到src/marsgis目录下面,其中 pages 目录非必须,可以按需拷贝。

2. 拷贝 public 下的资源

将基础项目public下所有文件拷贝到自己项目的public目录下。

3. package.json 依赖的融合

复制 package.json 依赖包,保证依赖存在且版本正确。

4. 修改项目别名等配置

修改vite.config.ts 配置文件中的项目别名配置和 process 相关配置

alias: {
  {
    find: /@mars\//,
    replacement: pathResolve('src/marsgis') + '/',
  }
}

define: {
  'process.env': {
    BASE_URL: '/',
  },
}

5. 修改初始化相关依赖

src/pages/index/widget-store.ts配置文件拷贝src/marsgis/widget-store.ts位置。

再在src/main.js文件中加载和初始化相关依赖。

import "mars3d-cesium/Build/Cesium/Widgets/widgets.css"
import "mars3d/mars3d.css"
import "@mars/assets/style/index.less"

import { createRoot } from "react-dom/client"
import MainView from "@mars/components/MarsWork/MainView"
import { generateWidgetView } from "@mars/common/store/widget"
import widgetState from "./widget-state"


const WidgetView = generateWidgetView(widgetState)

const reactApp = createRoot(document.getElementById("root"))

reactApp.render(
  <MainView>
    <WidgetView></WidgetView>
  </MainView>
)

6.复制对应 react 页面代码

复制对应页面代码到组件中, 例如拷贝 src/pages/index/App.tsx 代码到自己项目需要展示地图的 react 文件中。

7. 合并项目规范等配置信息(可选)

如果没有下面文件,可以直接拷贝到自己项目中, 如果已有对应文件,可以对比参数,按需拷贝相关配置进已有文件中。

如果项目中采用的 eslint 标准库与基础项目不一致,则根据提示安装对应的依赖,相关依赖如下, 按照项目实际情况安装,并作相应调整.

8. 处理样式冲突

基础项目已经基本保证不会影响外部样式,此处要处理的是您项目中的全局样式对 mars3d 相关组件的影响。修改相关 CSS 保证基础项目功能 UI 正常即可。

开发中常见问题

1. 如何切换 mars3d 到授权版

参考 获取 Mars3D SDK类库中“从 Mars3D官网 下载获取”章节介绍。

流程大概是:

image

2. 局域网离线使用时注意事项

平台所有代码层面来说支持离线运行和使用的,但需要注意的是离线时的地图服务的相关处理。

如果局域网内有相关地形、卫星底图服务可以按内网服务类型和 URL 地址替换下config.json构造Map的代码中的默认地形和底图。

如果局域网内没有相关服务,可以按下面处理:

Mars3D 是什么

Mars3D平台火星科技研发的一款基于 WebGL 技术实现的三维客户端开发平台,基于Cesium优化提升与 B/S 架构设计,支持多行业扩展的轻量级高效能 GIS 开发平台,能够免安装、无插件地在浏览器中高效运行,并可快速接入与使用多种 GIS 数据和三维模型,呈现三维空间的可视化,完成平台在不同行业的灵活应用。

Mars3D 平台可用于构建无插件、跨操作系统、 跨浏览器的三维 GIS 应用程序。平台使用 WebGL 来进行硬件加速图形化,跨平台、跨浏览器来实现真正的动态大数据三维可视化。通过 Mars3D 产品可快速实现浏览器和移动端上美观、流畅的三维地图呈现与空间分析。

相关网站

版权说明

  1. Mars3D 平台由火星科技自主研发,拥有所有权利。
  2. 任何个人或组织可以在遵守相关要求下可以免费无限制使用。