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"
}六、进阶优化建议
- 瓦片缓存:使用
LruCache或磁盘缓存减少重复请求 - 手势支持:添加拖拽、缩放,配合
ReactProp更新center、zoom - WMTS GetCapabilities:解析服务元数据,自动适配
TileMatrixSet与层级 - iOS 支持:使用 Swift 实现对应
RCTViewManager和UIView子类
参考
Last updated on