diff --git a/docs/30_设计/MapLibre粒子Android发布与ReactNative包设计_v1.md b/docs/30_设计/MapLibre粒子Android发布与ReactNative包设计_v1.md new file mode 100644 index 000000000..6ae8ba67b --- /dev/null +++ b/docs/30_设计/MapLibre粒子Android发布与ReactNative包设计_v1.md @@ -0,0 +1,27 @@ +# MapLibre 粒子 Android 发布与 React Native 包设计 v1 + +## 版本与坐标 + +真实基础为 MapLibre Native Android `13.4.1`,共同 core commit 为 `18545ca10d296704b2cba675b182ff2c8da24588`,本发行只准备 Android OpenGL。正式规则为 `<上游版本>-particle.<修订号>`;首发为 `13.4.1-particle.1`。正式 npm 包为 `@navsea/maplibre-particle-rn@13.4.1-particle.1`,正式 Maven 坐标为 `cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.1`。 + +`step061` 是已经完成验证的开发阶段制品编号,不是正式发行版本。release manifest 是 `release/maplibre-particle/release-manifest.json`;registry URL 暂不决定,manifest 中 npm/Maven server 字段均为 null。 + +## Consumer 依赖关系 + +```text +Consumer + ↓ 直接安装 +@navsea/maplibre-particle-rn + ↓ 唯一 MapLibre RN native module +@maplibre/maplibre-react-native 11.3.6 + ↓ Gradle dependency substitution +cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.1 +``` + +正式包 re-export 已有 MLRN `Map`、`Camera` 等公共 API,并包含 WeatherRuntime TurboModule。它不是 MLRN 的完整 fork;`@maplibre/maplibre-react-native` 是正式包的内部依赖,而 Consumer 不应再直接安装第二份 MLRN。package plugin 把 MLRN 唯一的 `org.maplibre.gl:android-sdk-opengl` 请求替换为正式 NavSea AAR,避免官方 AAR 与 Particle AAR 同时进入 APK。 + +WeatherColorLayer 与 RasterParticleLayer 是 Core-only style layer,不是 React 组件;包公开 `createParticleWeatherStyle()` 和稳定 IDs 以声明样式。field 仍只经既有 `metadataJson + local file:// payload → WeatherRuntime → RasterArraySource::submitHostField()` 进入 Native/Core;不创建第二套 ingress、Timeline 或 Runtime Window。 + +## 发布与升级边界 + +Consumer 不编译 MapLibre Native 源码,也不使用 workspace、relative source path、CMake path 或软链接。registry 未决定时,`.tgz` 与 Maven Local/临时 staging 只用于预发布验证,不是最终部署决定。未来升级到新的上游版本时,按 `13.5.0-particle.1` 等新发行线重新完成 Native、tarball Consumer 和 APK 验证;通过后才单独创建 release tag。 diff --git a/docs/50_Step记录/STEP-063_MapLibre粒子Android_v1发布工程化_v1.md b/docs/50_Step记录/STEP-063_MapLibre粒子Android_v1发布工程化_v1.md new file mode 100644 index 000000000..4c5790bc3 --- /dev/null +++ b/docs/50_Step记录/STEP-063_MapLibre粒子Android_v1发布工程化_v1.md @@ -0,0 +1,27 @@ +# STEP-063 MapLibre 粒子 Android v1 发布工程化 v1 + +状态:`IMPLEMENTED / WAITING_MANUAL_RELEASE_VALIDATION` + +## 目标与基线 + +- 实际 MapLibre Native Android 基础版本:`13.4.1`;core:`18545ca10d296704b2cba675b182ff2c8da24588`;renderer:OpenGL。 +- 正式版本:`13.4.1-particle.1`;npm:`@navsea/maplibre-particle-rn@13.4.1-particle.1`;Maven:`cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.1`。 +- STEP-061 的 `13.4.1-step061` 是开发期已验收坐标,不作为正式 release/package/build config。 + +## 已完成 + +- [x] 建立机器可读 release manifest,registry 均明确为 `null`。 +- [x] Android publication 配置改为正式 group/artifact/version,保留 OpenGL publication postfix。 +- [x] 建立正式 RN facade package,含 JS/TS source、类型、Android bridge、autolinking、Expo plugin、README、CHANGELOG、LICENSE。 +- [x] package plugin 将 MLRN 的单一 OpenGL SDK dependency substitution 到正式 NavSea AAR,避免双 Native AAR。 +- [x] 建立只声明正式 npm 包的 Consumer Test Host;不引用源码 package、Native tree 或复制 bridge。 +- [x] `npm pack`、tarball 内容审计、package JSON、plugin syntax、package TypeScript check、`git diff --check` 静态验证。 +- [x] 设计、Step、完成报告和开发记录。 + +## 禁止项与未执行项 + +未改 Particle/Core/JNI/WeatherRuntime 业务功能,未接入 NavSea,未执行 Android Gradle、AAR build/publish、Maven Local publish、APK、ADB 或 Jest。未创建 release tag,未决定 npm/Maven server。 + +## 下一步 + +用户手工构建并在 Maven Local 或临时 staging 准备 `cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.1`;以 npm tarball 安装 Consumer Host,手工编译/安装 APK 并验证 GSI、WeatherColor、Particle 与 Runtime READY。全部通过后才可进入 release freeze/tag 任务。 diff --git a/docs/报告/STEP-063_MapLibre粒子Android_v1发布工程化_Codex完成报告_v1.md b/docs/报告/STEP-063_MapLibre粒子Android_v1发布工程化_Codex完成报告_v1.md new file mode 100644 index 000000000..7e0fbd1c7 --- /dev/null +++ b/docs/报告/STEP-063_MapLibre粒子Android_v1发布工程化_Codex完成报告_v1.md @@ -0,0 +1,42 @@ +# STEP-063 MapLibre 粒子 Android v1 发布工程化 — Codex完成报告 v1 + +结论:`IMPLEMENTED / WAITING_MANUAL_RELEASE_VALIDATION`。 + +## 真实基线与发布契约 + +实际确认 upstream Android 基础为 `13.4.1`,共同 core 为 `18545ca10d296704b2cba675b182ff2c8da24588`,当前发行 renderer 为 OpenGL。正式版本采用 `13.4.1-particle.1`;release manifest 位于 `release/maplibre-particle/release-manifest.json`。正式 npm 包路径为 `packages/maplibre-particle-rn/`,名称为 `@navsea/maplibre-particle-rn`;正式 Maven 坐标为 `cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.1`。 + +原开发 AAR `org.maplibre.gl:android-sdk-opengl:13.4.1-step061` 只保留在历史 Step/验收说明中。正式 Android publication、RN package Android bridge 与 package plugin 均指向正式 NavSea coordinate;未继续把 step061 用作当前发行依赖。 + +## RN package 与 Consumer + +正式 package 保留 Native WeatherRuntime bridge,re-export 当前 MLRN 公共 Map API。当前仓库不是完整 MLRN fork,故 package 将 `@maplibre/maplibre-react-native@11.3.6` 作为内部依赖;Consumer 只直接安装 NavSea package,不应额外直接安装官方 MLRN。package plugin 利用 Gradle dependency substitution,将 MLRN 的唯一 OpenGL SDK 请求替换为正式 NavSea AAR,避免两份 Native AAR。 + +Consumer Host 位于 `integration/react-native/MapLibreParticlePackageConsumerTestHost/`,只声明 `@navsea/maplibre-particle-rn@13.4.1-particle.1`,从其导入 Map、Camera、WeatherRuntime 和 style helper;没有 `file:`、workspace/source-path package dependency,也不复制 bridge 或引用 Native source tree。 + +## tarball 与静态验证 + +`npm pack` 成功生成 `navsea-maplibre-particle-rn-13.4.1-particle.1.tgz`(8,951 bytes,SHA-1 `2127d7b0e7ce71d0d9e429c92806cd93799c919e`)至 `/tmp/step063-package.*`。tarball 含 package JSON、TS source/types、Android bridge/Gradle、autolinking config、Expo plugin、README、CHANGELOG、LICENSE;不含 node_modules、.git、build cache、APK、Test Host、WeatherV2 assets、logs、无关 docs 或凭证。 + +package JSON 和两个 plugin Node syntax 检查 PASS;正式 package TypeScript check PASS(经 build-output-throttle);release manifest/package/Consumer version contract、无 `file:` consumer dependency、正式 build config 无 step061 与 `git diff --check` 均 PASS。Consumer 依赖的是尚未上传 registry 的正式版本,故本轮不执行其 `npm install`;这由后续 tarball 安装人工验证覆盖。没有运行 Jest。 + +## 未执行的人工验证与命令 + +Codex 未执行 Android Gradle、AAR build/publish、Maven Local publish、APK、ADB 或实体机测试。用户后续在具备 Android 环境时执行: + +```bash +cd platform/android +# 按当前正式 publication 配置手工构建并发布到 Maven Local 或临时 staging。 +./gradlew :MapLibreAndroid:publishOpenglreleasePublicationToMavenLocal --console=plain + +cd packages/maplibre-particle-rn +npm pack --pack-destination /tmp/maplibre-particle-release + +cd integration/react-native/MapLibreParticlePackageConsumerTestHost +npm install /tmp/maplibre-particle-release/navsea-maplibre-particle-rn-13.4.1-particle.1.tgz --save-exact +npm install --ignore-scripts +npx expo prebuild --platform android --clean +npx expo run:android +``` + +随后验证 GSI、WeatherColor、Particle、canonical fixture submit 与 Runtime READY。通过前不得创建正式 release tag,也不得写 `RELEASED` 或 `CLOSED / PASS`。 diff --git a/integration/react-native/MapLibreParticlePackageConsumerTestHost/App.tsx b/integration/react-native/MapLibreParticlePackageConsumerTestHost/App.tsx new file mode 100644 index 000000000..3bc33e518 --- /dev/null +++ b/integration/react-native/MapLibreParticlePackageConsumerTestHost/App.tsx @@ -0,0 +1,26 @@ +import { useCallback, useState } from "react"; +import { Button, SafeAreaView, StyleSheet, Text, View, findNodeHandle } from "react-native"; +import { File, Paths } from "expo-file-system"; +import { Camera, Map, WeatherRuntime, createParticleWeatherStyle } from "@navsea/maplibre-particle-rn"; + +const metadata = JSON.stringify({ schema: "maplibre-particle-field/v1", fieldId: "consumer-smoke", fieldType: "WIND", displayDomain: "GLOBAL", generation: 1, validTime: 1786060800000, dataVersion: 1, bounds: { west: -180, south: -85, east: 180, north: 85 }, grid: { width: 2, height: 2 }, vector: { layout: "interleaved-uv", encoding: "float32-le", noData: -9999 }, payload: { byteLength: 32 } }); + +function writeFixture() { + const bytes = new Uint8Array(32); const view = new DataView(bytes.buffer); + for (let offset = 0; offset < bytes.byteLength; offset += 8) { view.setFloat32(offset, 5, true); view.setFloat32(offset + 4, 1, true); } + const file = new File(Paths.cache, "navsea-particle-consumer-smoke.f32"); file.write(bytes); return file.uri; +} + +export default function App() { + const [tag, setTag] = useState(null); const [state, setState] = useState("STYLE_LOADING"); + const onStyle = useCallback(async (event: unknown) => { + const value = findNodeHandle((event as { target?: unknown }).target as never); if (value == null) return; + try { await WeatherRuntime.attach(value); setTag(value); setState("WAITING_FOR_FIELDS"); } catch (error) { setState(String(error)); } + }, []); + const submit = useCallback(async () => { + if (tag == null) return; + try { await WeatherRuntime.clearWindFields(tag); const result = await WeatherRuntime.submitWindField(tag, metadata, writeFixture()); const runtime = await WeatherRuntime.getState(tag); setState(`${result} / ${runtime.state}`); } catch (error) { setState(String(error)); } + }, [tag]); + return Package smoke: {state}