Skip to Content
文档地图RN 封装 WMTS 组件

RN 封装 WMTS 组件

本教程介绍如何使用 Kotlin 在 React Native 中封装 WMTS(Web Map Tile Service)地图瓦片组件,并在 RN 项目中引入使用。

前置条件

  • Node.js 18+
  • Android Studio(含 Kotlin 支持)
  • React Native 开发环境
  • JDK 17
  • 基础的 Kotlin 与 React Native 知识

WMTS 简介

WMTS 是 OGC 标准的网络地图瓦片服务,瓦片 URL 通常格式为:

{baseUrl}/{layer}/{style}/{TileMatrixSet}/{z}/{y}/{x}.{format}

例如天地图: https://t0.tianditu.gov.cn/vec_w/wmts?layer=vec&style=default&tilematrixset=w&SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&FORMAT=tiles

一、创建 React Native 项目

npx react-native@latest init MapApp cd MapApp

二、Android 原生模块结构

android/app/src/main/java/com/mapapp/ 下创建以下文件:

2.1 WMTS 瓦片视图(Kotlin)

// android/app/src/main/java/com/mapapp/WmtsMapView.kt package com.mapapp import android.content.Context import android.widget.FrameLayout import android.widget.ImageView import android.widget.LinearLayout import android.graphics.BitmapFactory import kotlinx.coroutines.* import java.net.URL import android.util.DisplayMetrics import kotlin.math.* class WmtsMapView(context: Context) : FrameLayout(context) { private val scope = CoroutineScope(Dispatchers.Main + Job()) internal var baseUrl: String = "" internal var zoom: Int = 10 internal var centerX: Double = 116.4 internal var centerY: Double = 39.9 private var tileSize: Int = 256 private val tileCache = mutableMapOf<String, android.graphics.Bitmap?>() fun setConfig(url: String, zoomLevel: Int, lng: Double, lat: Double) { baseUrl = url zoom = zoomLevel centerX = lng centerY = lat loadTiles() } private fun lngToTileX(lng: Double, zoom: Int): Int { return ((lng + 180) / 360 * (1 shl zoom)).toInt() } private fun latToTileY(lat: Double, zoom: Int): Int { val latRad = Math.toRadians(lat) return ((1 - ln(tan(latRad) + 1 / cos(latRad)) / Math.PI) / 2 * (1 shl zoom)).toInt() } private fun loadTiles() { removeAllViews() val container = LinearLayout(context).apply { orientation = LinearLayout.VERTICAL layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) } val centerTileX = lngToTileX(centerX, zoom) val centerTileY = latToTileY(centerY, zoom) val displayMetrics: DisplayMetrics = context.resources.displayMetrics val screenWidth = displayMetrics.widthPixels val screenHeight = displayMetrics.heightPixels val tilesWide = (screenWidth / tileSize) + 2 val tilesHigh = (screenHeight / tileSize) + 2 for (row in 0 until tilesHigh) { val rowLayout = LinearLayout(context).apply { orientation = LinearLayout.HORIZONTAL } for (col in 0 until tilesWide) { val tileX = centerTileX - tilesWide / 2 + col val tileY = centerTileY - tilesHigh / 2 + row val tileUrl = baseUrl .replace("{z}", zoom.toString()) .replace("{x}", tileX.toString()) .replace("{y}", tileY.toString()) val imageView = ImageView(context).apply { layoutParams = LinearLayout.LayoutParams(tileSize, tileSize) } scope.launch { val bitmap = withContext(Dispatchers.IO) { loadTileBitmap(tileUrl) } bitmap?.let { imageView.setImageBitmap(it) } } rowLayout.addView(imageView) } container.addView(rowLayout) } addView(container) } private fun loadTileBitmap(url: String): android.graphics.Bitmap? { return tileCache.getOrPut(url) { try { BitmapFactory.decodeStream(URL(url).openStream()) } catch (e: Exception) { null } } } override fun onDetachedFromWindow() { super.onDetachedFromWindow() scope.cancel() } }

2.2 ViewManager

// android/app/src/main/java/com/mapapp/WmtsMapViewManager.kt package com.mapapp import com.facebook.react.bridge.ReadableMap import com.facebook.react.uimanager.SimpleViewManager import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.annotations.ReactProp class WmtsMapViewManager : SimpleViewManager<WmtsMapView>() { override fun getName() = "WmtsMapView" override fun createViewInstance(reactContext: ThemedReactContext): WmtsMapView { return WmtsMapView(reactContext) } @ReactProp(name = "url") fun setUrl(view: WmtsMapView, url: String?) { url?.let { view.setConfig(it, view.zoom, view.centerX, view.centerY) } } @ReactProp(name = "zoom") fun setZoom(view: WmtsMapView, zoom: Int) { view.setConfig(view.baseUrl, zoom, view.centerX, view.centerY) } @ReactProp(name = "center") fun setCenter(view: WmtsMapView, center: ReadableMap?) { center?.let { val lng = it.getDouble("longitude") val lat = it.getDouble("latitude") view.setConfig(view.baseUrl, view.zoom, lng, lat) } } }

2.3 Package 注册

// android/app/src/main/java/com/mapapp/WmtsMapPackage.kt package com.mapapp import com.facebook.react.ReactPackage import com.facebook.react.bridge.NativeModule import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.uimanager.ViewManager class WmtsMapPackage : ReactPackage { override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> { return emptyList() } override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> { return listOf(WmtsMapViewManager()) } }

2.4 在 MainApplication 中注册

// android/app/src/main/java/com/mapapp/MainApplication.kt (片段) override fun getPackages(): List<ReactPackage> = PackageList(this).packages.apply { add(WmtsMapPackage()) }

三、JavaScript 侧封装

3.1 创建 WMTS 组件

// src/components/WmtsMap.js import React from 'react'; import { requireNativeComponent, View } from 'react-native'; const NativeWmtsMap = requireNativeComponent('WmtsMapView'); export default function WmtsMap({ url, zoom = 10, center = { longitude: 116.4, latitude: 39.9 }, style }) { return ( <NativeWmtsMap url={url} zoom={zoom} center={center} style={[{ flex: 1 }, style]} /> ); }

3.2 TypeScript 类型定义(可选)

// src/components/WmtsMap.d.ts export interface WmtsMapProps { url: string; zoom?: number; center?: { longitude: number; latitude: number }; style?: object; }

四、在 RN 中引入使用

4.1 在页面中使用

// App.js import React from 'react'; import { SafeAreaView, StyleSheet } from 'react-native'; import WmtsMap from './src/components/WmtsMap'; // 天地图矢量瓦片示例 URL 模板 const TIAN_MAP_URL = 'https://t0.tianditu.gov.cn/vec_w/wmts?layer=vec&style=default&tilematrixset=w&SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&FORMAT=tiles'; export default function App() { return ( <SafeAreaView style={styles.container}> <WmtsMap url={TIAN_MAP_URL} zoom={12} center={{ longitude: 116.397428, latitude: 39.90923 }} /> </SafeAreaView> ); } const styles = StyleSheet.create({ container: { flex: 1, }, });

4.2 常见 WMTS URL 模板

服务商URL 模板示例
天地图...TILEMATRIX={z}&TILEROW={y}&TILECOL={x}...
GeoServer.../{z}/{y}/{x}.png
ArcGIS.../tile/{z}/{y}/{x}

注意:

  • 不同服务的参数命名可能为 {z}/{y}/{x}TILEMATRIX/TILEROW/TILECOL,封装时可做 URL 适配层
  • 天地图等商用服务在生产环境需申请 API Key 并加入 URL 参数

五、依赖说明

android/build.gradle 中确保:

// Kotlin buildscript { ext.kotlin_version = '1.9.0' }

android/app/build.gradle 中:

dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3" }

六、进阶优化建议

  1. 瓦片缓存:使用 LruCache 或磁盘缓存减少重复请求
  2. 手势支持:添加拖拽、缩放,配合 ReactProp 更新 centerzoom
  3. WMTS GetCapabilities:解析服务元数据,自动适配 TileMatrixSet 与层级
  4. iOS 支持:使用 Swift 实现对应 RCTViewManagerUIView 子类

参考

Last updated on