@drawtonomy/sdk 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +36 -0
- package/README.md +16 -2
- package/dist/exporter/index.d.ts +4 -0
- package/dist/exporter/index.d.ts.map +1 -1
- package/dist/exporter/index.js +9 -3
- package/dist/exporter/index.js.map +1 -1
- package/dist/exporter/lanelet2.d.ts +26 -0
- package/dist/exporter/lanelet2.d.ts.map +1 -0
- package/dist/exporter/lanelet2.js +261 -0
- package/dist/exporter/lanelet2.js.map +1 -0
- package/dist/exporter/opendrive.d.ts.map +1 -1
- package/dist/exporter/opendrive.js +54 -1
- package/dist/exporter/opendrive.js.map +1 -1
- package/dist/exporter/osmParser.d.ts +54 -0
- package/dist/exporter/osmParser.d.ts.map +1 -0
- package/dist/exporter/osmParser.js +247 -0
- package/dist/exporter/osmParser.js.map +1 -0
- package/dist/exporter/osmToShapes.d.ts +107 -0
- package/dist/exporter/osmToShapes.d.ts.map +1 -0
- package/dist/exporter/osmToShapes.js +370 -0
- package/dist/exporter/osmToShapes.js.map +1 -0
- package/dist/exporter/projection.d.ts +36 -0
- package/dist/exporter/projection.d.ts.map +1 -0
- package/dist/exporter/projection.js +65 -0
- package/dist/exporter/projection.js.map +1 -0
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -6
- package/LICENSE +0 -190
package/README.ja.md
CHANGED
|
@@ -118,6 +118,42 @@ https://drawtonomy.com?ext=http://localhost:3001/manifest.json
|
|
|
118
118
|
| `getBoundingBox(points)` | バウンディングボックスを取得 |
|
|
119
119
|
| `distanceToSegment(point, a, b)` | 点からセグメントまでの距離 |
|
|
120
120
|
|
|
121
|
+
### Exporter モジュール
|
|
122
|
+
|
|
123
|
+
`DrawtonomySnapshot` を OpenDRIVE / OpenSCENARIO / Lanelet2 OSM 形式の文字列に変換します。エディタランタイムに依存しないため、ヘッドレスツール、サーバーサイドパイプライン、ブラウザ拡張等から利用可能です。Lanelet2 については OSM XML をエディタが扱える形式(point / linestring / lane)に戻すパーサも提供し、ラウンドトリップを実現します。
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { exporter, createSnapshot } from '@drawtonomy/sdk'
|
|
127
|
+
|
|
128
|
+
const snapshot = createSnapshot(myShapes)
|
|
129
|
+
const xodr = exporter.exportToOpenDrive(snapshot)
|
|
130
|
+
const xosc = exporter.exportToOpenScenario(snapshot, { xodrFilename: 'scene.xodr' })
|
|
131
|
+
|
|
132
|
+
// .xodr + .xosc を 1 つの zip にまとめて esmini で実行可能に
|
|
133
|
+
const { blob, baseName } = exporter.buildEsminiZip(snapshot, { baseName: 'my-scene' })
|
|
134
|
+
|
|
135
|
+
// Lanelet2 (.osm XML) のエクスポート + 再インポート
|
|
136
|
+
const osm = exporter.exportToLanelet2(snapshot, { mapOrigin: { lat: 35.0, lon: 139.0 } })
|
|
137
|
+
const data = exporter.parseOsmXml(osm)
|
|
138
|
+
const imported = exporter.osmToShapes(data)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
| 関数 | 説明 |
|
|
142
|
+
|------|------|
|
|
143
|
+
| `exporter.exportToOpenDrive(snapshot)` | OpenDRIVE 1.8 (.xodr) XML |
|
|
144
|
+
| `exporter.exportToOpenScenario(snapshot, options?)` | OpenSCENARIO 1.3 (.xosc) XML |
|
|
145
|
+
| `exporter.buildEsminiZip(snapshot, options?)` | .xodr + .xosc を 1 つの zip にまとめる |
|
|
146
|
+
| `exporter.exportToLanelet2(snapshot, options?)` | Lanelet2 (.osm XML) ドキュメント |
|
|
147
|
+
| `exporter.parseOsmXml(xml)` | Lanelet2 OSM XML を構造化データへパース |
|
|
148
|
+
| `exporter.osmToShapes(data, options?)` | OSM → エディタが使う point / linestring / lane レコード |
|
|
149
|
+
| `exporter.alignBoundaries(left, right)` | レーンの左右境界の反転フラグを判定 |
|
|
150
|
+
| `exporter.createShapeIdAllocator()` | `osmToShapes` で使用する ID アロケータ |
|
|
151
|
+
| `exporter.latLonToCanvas(lat, lon, ...)` / `canvasToLatLon(...)` | 等距円筒投影ヘルパ |
|
|
152
|
+
| `exporter.buildPathTrajectory(input)` | Path → 時刻付き頂点列 |
|
|
153
|
+
| `exporter.computeCenterlineWithWidth(left, right)` | レーン中心線 + 幅サンプル |
|
|
154
|
+
| `exporter.buildZip(entries)` | 純粋な ZIP ビルダー (store mode、依存なし) |
|
|
155
|
+
| `exporter.sanitizeFileBaseName(input)` | OS-safe なベース名サニタイザ |
|
|
156
|
+
|
|
121
157
|
## デプロイ
|
|
122
158
|
|
|
123
159
|
エクステンションは任意のHTTPSホスティングサービスにデプロイできます。
|
package/README.md
CHANGED
|
@@ -129,8 +129,11 @@ http://localhost:3000/?ext=http://localhost:3001/manifest.json
|
|
|
129
129
|
### Exporter Module
|
|
130
130
|
|
|
131
131
|
Convert a `DrawtonomySnapshot` into target-format strings (OpenDRIVE,
|
|
132
|
-
OpenSCENARIO) without depending on the editor runtime — useful
|
|
133
|
-
tooling, server-side pipelines, or browser extensions.
|
|
132
|
+
OpenSCENARIO, Lanelet2 OSM) without depending on the editor runtime — useful
|
|
133
|
+
for headless tooling, server-side pipelines, or browser extensions. The
|
|
134
|
+
Lanelet2 module additionally exposes a parser that turns OSM XML back into
|
|
135
|
+
editor-ready primitives (points / linestrings / lanes), enabling round-trip
|
|
136
|
+
workflows.
|
|
134
137
|
|
|
135
138
|
```typescript
|
|
136
139
|
import { exporter, createSnapshot } from '@drawtonomy/sdk'
|
|
@@ -141,6 +144,11 @@ const xosc = exporter.exportToOpenScenario(snapshot, { xodrFilename: 'scene.xodr
|
|
|
141
144
|
|
|
142
145
|
// Bundle both into a single .zip ready for esmini.
|
|
143
146
|
const { blob, baseName } = exporter.buildEsminiZip(snapshot, { baseName: 'my-scene' })
|
|
147
|
+
|
|
148
|
+
// Lanelet2 (.osm XML) export and round-trip.
|
|
149
|
+
const osm = exporter.exportToLanelet2(snapshot, { mapOrigin: { lat: 35.0, lon: 139.0 } })
|
|
150
|
+
const data = exporter.parseOsmXml(osm)
|
|
151
|
+
const imported = exporter.osmToShapes(data)
|
|
144
152
|
```
|
|
145
153
|
|
|
146
154
|
| Function | Description |
|
|
@@ -148,6 +156,12 @@ const { blob, baseName } = exporter.buildEsminiZip(snapshot, { baseName: 'my-sce
|
|
|
148
156
|
| `exporter.exportToOpenDrive(snapshot)` | OpenDRIVE 1.8 (.xodr) XML |
|
|
149
157
|
| `exporter.exportToOpenScenario(snapshot, options?)` | OpenSCENARIO 1.3 (.xosc) XML |
|
|
150
158
|
| `exporter.buildEsminiZip(snapshot, options?)` | One-shot zip bundling .xodr + .xosc |
|
|
159
|
+
| `exporter.exportToLanelet2(snapshot, options?)` | Lanelet2 (.osm XML) document |
|
|
160
|
+
| `exporter.parseOsmXml(xml)` | Parse Lanelet2 OSM XML into structured data |
|
|
161
|
+
| `exporter.osmToShapes(data, options?)` | OSM → editor-ready point / linestring / lane records |
|
|
162
|
+
| `exporter.alignBoundaries(left, right)` | Decide invert flags for a lane's left / right boundary |
|
|
163
|
+
| `exporter.createShapeIdAllocator()` | Pluggable id allocator used by `osmToShapes` |
|
|
164
|
+
| `exporter.latLonToCanvas(lat, lon, ...)` / `canvasToLatLon(...)` | Equirectangular projection helpers |
|
|
151
165
|
| `exporter.buildPathTrajectory(input)` | Path → time-stamped vertex sequence |
|
|
152
166
|
| `exporter.computeCenterlineWithWidth(left, right)` | Lane centerline + width samples |
|
|
153
167
|
| `exporter.buildZip(entries)` | Pure ZIP builder (store mode, no deps) |
|
package/dist/exporter/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { exportToOpenDrive } from './opendrive';
|
|
2
|
+
export { latLonToTmercProj, latLonToUtmProj, originToProjString, FALLBACK_GEO_REFERENCE, type GeoOrigin, } from './projection';
|
|
2
3
|
export { exportToOpenScenario, templateIdToVehicleCategory, type OpenScenarioExportOptions, type TemplateResolver, } from './openscenario';
|
|
3
4
|
export { buildPathTrajectory, DEFAULT_PATH_SPEED_MPS, type PathSamplePoint, type PathTrajectoryInput, } from './trajectory';
|
|
4
5
|
export { computeCenterlineWithWidth, sampleAtParam, computeHeadings, type Point2D, type CenterlineSample, } from './laneCenterline';
|
|
@@ -6,4 +7,7 @@ export { buildEsminiZip, type EsminiPackageOptions, type EsminiPackageResult } f
|
|
|
6
7
|
export { buildZip, type ZipEntry } from './zip';
|
|
7
8
|
export { sanitizeFileBaseName } from './sanitize';
|
|
8
9
|
export { PIXELS_PER_METER } from './units';
|
|
10
|
+
export { exportToLanelet2, DEFAULT_ORIGIN_LAT, DEFAULT_ORIGIN_LON, type Lanelet2ExportOptions, type OsmSidecar, type MapOrigin, } from './lanelet2';
|
|
11
|
+
export { parseOsmXml, latLonToCanvas, canvasToLatLon, type OsmData, type OsmNode, type OsmWay, type OsmRelation, } from './osmParser';
|
|
12
|
+
export { osmToShapes, alignBoundaries, createShapeIdAllocator, type ImportedShapes, type ImportedPoint, type ImportedLinestring, type ImportedLane, type ImportBounds, type OsmToShapesOptions, type ShapeIdAllocator, } from './osmToShapes';
|
|
9
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/exporter/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/exporter/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,sBAAsB,EACtB,KAAK,SAAS,GACf,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,oBAAoB,EACpB,2BAA2B,EAC3B,KAAK,yBAAyB,EAC9B,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,KAAK,eAAe,EACpB,KAAK,mBAAmB,GACzB,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,0BAA0B,EAC1B,aAAa,EACb,eAAe,EACf,KAAK,OAAO,EACZ,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,cAAc,EAAE,KAAK,oBAAoB,EAAE,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AACrG,OAAO,EAAE,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;AAC/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,KAAK,qBAAqB,EAC1B,KAAK,UAAU,EACf,KAAK,SAAS,GACf,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,WAAW,EACX,cAAc,EACd,cAAc,EACd,KAAK,OAAO,EACZ,KAAK,OAAO,EACZ,KAAK,MAAM,EACX,KAAK,WAAW,GACjB,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,WAAW,EACX,eAAe,EACf,sBAAsB,EACtB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACtB,MAAM,eAAe,CAAA"}
|
package/dist/exporter/index.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
// drawtonomy SDK — exporter modules.
|
|
2
2
|
//
|
|
3
3
|
// These exporters convert a `DrawtonomySnapshot` into target-format strings
|
|
4
|
-
// (OpenDRIVE / OpenSCENARIO) without depending on the editor
|
|
5
|
-
// they can be used in headless tooling, server-side pipelines, or
|
|
6
|
-
// extensions.
|
|
4
|
+
// (OpenDRIVE / OpenSCENARIO / Lanelet2 OSM) without depending on the editor
|
|
5
|
+
// runtime, so they can be used in headless tooling, server-side pipelines, or
|
|
6
|
+
// browser extensions. The Lanelet2 module additionally exposes a parser that
|
|
7
|
+
// turns OSM XML back into editor-ready primitives (points / linestrings /
|
|
8
|
+
// lanes), enabling round-trip workflows.
|
|
7
9
|
export { exportToOpenDrive } from './opendrive';
|
|
10
|
+
export { latLonToTmercProj, latLonToUtmProj, originToProjString, FALLBACK_GEO_REFERENCE, } from './projection';
|
|
8
11
|
export { exportToOpenScenario, templateIdToVehicleCategory, } from './openscenario';
|
|
9
12
|
export { buildPathTrajectory, DEFAULT_PATH_SPEED_MPS, } from './trajectory';
|
|
10
13
|
export { computeCenterlineWithWidth, sampleAtParam, computeHeadings, } from './laneCenterline';
|
|
@@ -12,4 +15,7 @@ export { buildEsminiZip } from './packageEsmini';
|
|
|
12
15
|
export { buildZip } from './zip';
|
|
13
16
|
export { sanitizeFileBaseName } from './sanitize';
|
|
14
17
|
export { PIXELS_PER_METER } from './units';
|
|
18
|
+
export { exportToLanelet2, DEFAULT_ORIGIN_LAT, DEFAULT_ORIGIN_LON, } from './lanelet2';
|
|
19
|
+
export { parseOsmXml, latLonToCanvas, canvasToLatLon, } from './osmParser';
|
|
20
|
+
export { osmToShapes, alignBoundaries, createShapeIdAllocator, } from './osmToShapes';
|
|
15
21
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/exporter/index.ts"],"names":[],"mappings":"AAAA,qCAAqC;AACrC,EAAE;AACF,4EAA4E;AAC5E,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/exporter/index.ts"],"names":[],"mappings":"AAAA,qCAAqC;AACrC,EAAE;AACF,4EAA4E;AAC5E,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,0EAA0E;AAC1E,yCAAyC;AAEzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,sBAAsB,GAEvB,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,oBAAoB,EACpB,2BAA2B,GAG5B,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACL,mBAAmB,EACnB,sBAAsB,GAGvB,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,0BAA0B,EAC1B,aAAa,EACb,eAAe,GAGhB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,cAAc,EAAuD,MAAM,iBAAiB,CAAA;AACrG,OAAO,EAAE,QAAQ,EAAiB,MAAM,OAAO,CAAA;AAC/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,GAInB,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,WAAW,EACX,cAAc,EACd,cAAc,GAKf,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,WAAW,EACX,eAAe,EACf,sBAAsB,GAQvB,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { DrawtonomySnapshot } from '../types';
|
|
2
|
+
/** Sidecar captured at OSM import time. Used to round-trip tags / `ele`. */
|
|
3
|
+
export interface OsmSidecar {
|
|
4
|
+
rawXml: string;
|
|
5
|
+
originLat: number;
|
|
6
|
+
originLon: number;
|
|
7
|
+
}
|
|
8
|
+
/** Origin to use when no sidecar is provided. */
|
|
9
|
+
export interface MapOrigin {
|
|
10
|
+
lat: number | null;
|
|
11
|
+
lon: number | null;
|
|
12
|
+
}
|
|
13
|
+
export interface Lanelet2ExportOptions {
|
|
14
|
+
sidecar?: OsmSidecar | null;
|
|
15
|
+
mapOrigin?: MapOrigin | null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Default origin used when neither a sidecar nor a map origin is supplied.
|
|
19
|
+
* Tokyo Teleport Station — picked because it is in a flat area visible at
|
|
20
|
+
* default zoom on most map providers.
|
|
21
|
+
*/
|
|
22
|
+
export declare const DEFAULT_ORIGIN_LAT = 35.62614;
|
|
23
|
+
export declare const DEFAULT_ORIGIN_LON = 139.77525;
|
|
24
|
+
/** Build a Lanelet2 OSM XML document from a snapshot. */
|
|
25
|
+
export declare function exportToLanelet2(snapshot: DrawtonomySnapshot, options?: Lanelet2ExportOptions): string;
|
|
26
|
+
//# sourceMappingURL=lanelet2.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lanelet2.d.ts","sourceRoot":"","sources":["../../src/exporter/lanelet2.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAEV,kBAAkB,EAInB,MAAM,UAAU,CAAA;AAOjB,4EAA4E;AAC5E,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,iDAAiD;AACjD,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAA;IAC3B,SAAS,CAAC,EAAE,SAAS,GAAG,IAAI,CAAA;CAC7B;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,WAAW,CAAA;AAC1C,eAAO,MAAM,kBAAkB,YAAY,CAAA;AAwN3C,yDAAyD;AACzD,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,GAAE,qBAA0B,GAClC,MAAM,CAiDR"}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
// Lanelet2 (.osm XML) exporter — emits an OSM document from a snapshot.
|
|
2
|
+
// No external library dependencies.
|
|
3
|
+
//
|
|
4
|
+
// Behavior summary:
|
|
5
|
+
// - Each PointShape becomes a `<node>`
|
|
6
|
+
// - Each LinestringShape becomes a `<way>` referencing its point nodes
|
|
7
|
+
// - Each LaneShape becomes a `<relation type=lanelet>` referencing its
|
|
8
|
+
// left/right way as members
|
|
9
|
+
// - When a sidecar (the original OSM XML captured at import time) is supplied,
|
|
10
|
+
// tags / `ele` / unrelated relations (regulatory_element etc.) are
|
|
11
|
+
// round-tripped: shape-derived entries override the sidecar copies for the
|
|
12
|
+
// same OSM IDs, and untouched entries are emitted verbatim in the original
|
|
13
|
+
// order.
|
|
14
|
+
// - The root `<osm>` element carries `drawtonomy_origin_lat` /
|
|
15
|
+
// `drawtonomy_origin_lon` so re-importing the file restores the same canvas
|
|
16
|
+
// origin. Standard OSM consumers ignore unknown attributes.
|
|
17
|
+
import { canvasToLatLon, parseOsmXml } from './osmParser';
|
|
18
|
+
/**
|
|
19
|
+
* Default origin used when neither a sidecar nor a map origin is supplied.
|
|
20
|
+
* Tokyo Teleport Station — picked because it is in a flat area visible at
|
|
21
|
+
* default zoom on most map providers.
|
|
22
|
+
*/
|
|
23
|
+
export const DEFAULT_ORIGIN_LAT = 35.62614;
|
|
24
|
+
export const DEFAULT_ORIGIN_LON = 139.77525;
|
|
25
|
+
function escapeXml(s) {
|
|
26
|
+
return s
|
|
27
|
+
.replace(/&/g, '&')
|
|
28
|
+
.replace(/</g, '<')
|
|
29
|
+
.replace(/>/g, '>')
|
|
30
|
+
.replace(/"/g, '"')
|
|
31
|
+
.replace(/'/g, ''');
|
|
32
|
+
}
|
|
33
|
+
function formatLatLon(v) {
|
|
34
|
+
// 11 decimal places ≈ 0.1 mm precision.
|
|
35
|
+
return v.toFixed(11);
|
|
36
|
+
}
|
|
37
|
+
function formatEle(v) {
|
|
38
|
+
// 3 decimal places = 1 mm precision.
|
|
39
|
+
return v.toFixed(3);
|
|
40
|
+
}
|
|
41
|
+
function buildShapeMap(shapes) {
|
|
42
|
+
const map = new Map();
|
|
43
|
+
for (const s of shapes)
|
|
44
|
+
map.set(s.id, s);
|
|
45
|
+
return map;
|
|
46
|
+
}
|
|
47
|
+
function buildFromShapes(shapes, originLat, originLon, sidecarData) {
|
|
48
|
+
const shapeMap = buildShapeMap(shapes);
|
|
49
|
+
const points = [];
|
|
50
|
+
const linestrings = [];
|
|
51
|
+
const lanes = [];
|
|
52
|
+
for (const s of shapes) {
|
|
53
|
+
if (s.type === 'point')
|
|
54
|
+
points.push(s);
|
|
55
|
+
else if (s.type === 'linestring')
|
|
56
|
+
linestrings.push(s);
|
|
57
|
+
else if (s.type === 'lane')
|
|
58
|
+
lanes.push(s);
|
|
59
|
+
}
|
|
60
|
+
const nodesOut = new Map();
|
|
61
|
+
const waysOut = new Map();
|
|
62
|
+
const relationsOut = [];
|
|
63
|
+
const shapeNodeOsmIds = new Set();
|
|
64
|
+
const shapeWayOsmIds = new Set();
|
|
65
|
+
const shapeRelationOsmIds = new Set();
|
|
66
|
+
// Resolve a shape -> OSM ID. Empty `osmId` (newly drawn shape) gets a fresh
|
|
67
|
+
// negative ID, matching the OSM convention for unsubmitted edits.
|
|
68
|
+
let nextNewId = -1;
|
|
69
|
+
const newIdFor = new Map();
|
|
70
|
+
const resolveOsmId = (shapeId, propsOsmId) => {
|
|
71
|
+
if (propsOsmId && propsOsmId.length > 0)
|
|
72
|
+
return propsOsmId;
|
|
73
|
+
const cached = newIdFor.get(shapeId);
|
|
74
|
+
if (cached !== undefined)
|
|
75
|
+
return cached;
|
|
76
|
+
const id = String(nextNewId--);
|
|
77
|
+
newIdFor.set(shapeId, id);
|
|
78
|
+
return id;
|
|
79
|
+
};
|
|
80
|
+
// Points -> nodes.
|
|
81
|
+
for (const p of points) {
|
|
82
|
+
const osmId = resolveOsmId(p.id, p.props.osmId);
|
|
83
|
+
shapeNodeOsmIds.add(osmId);
|
|
84
|
+
const { lat, lon } = canvasToLatLon(p.x, p.y, originLat, originLon);
|
|
85
|
+
const original = sidecarData?.nodes.get(osmId);
|
|
86
|
+
const tags = original ? { ...original.tags } : {};
|
|
87
|
+
const ele = original?.ele;
|
|
88
|
+
nodesOut.set(osmId, { id: osmId, lat, lon, ele, tags });
|
|
89
|
+
}
|
|
90
|
+
// Linestrings -> ways.
|
|
91
|
+
for (const ls of linestrings) {
|
|
92
|
+
const osmId = resolveOsmId(ls.id, ls.props.osmId);
|
|
93
|
+
shapeWayOsmIds.add(osmId);
|
|
94
|
+
const nodeRefs = [];
|
|
95
|
+
for (const pid of ls.props.pointIds) {
|
|
96
|
+
const point = shapeMap.get(pid);
|
|
97
|
+
if (!point)
|
|
98
|
+
continue;
|
|
99
|
+
nodeRefs.push(resolveOsmId(point.id, point.props.osmId));
|
|
100
|
+
}
|
|
101
|
+
const original = sidecarData?.ways.get(osmId);
|
|
102
|
+
const tags = original ? { ...original.tags } : {};
|
|
103
|
+
if (ls.props.attributes) {
|
|
104
|
+
for (const [k, v] of Object.entries(ls.props.attributes)) {
|
|
105
|
+
if (v !== undefined && v !== null && v !== '')
|
|
106
|
+
tags[k] = String(v);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (!tags.type)
|
|
110
|
+
tags.type = 'line_thin';
|
|
111
|
+
waysOut.set(osmId, { id: osmId, nodeRefs, tags });
|
|
112
|
+
}
|
|
113
|
+
// Lanes -> relations (type=lanelet).
|
|
114
|
+
for (const lane of lanes) {
|
|
115
|
+
const osmId = resolveOsmId(lane.id, lane.props.osmId);
|
|
116
|
+
shapeRelationOsmIds.add(osmId);
|
|
117
|
+
const original = sidecarData?.relations.find(r => r.id === osmId);
|
|
118
|
+
const members = [];
|
|
119
|
+
let leftWayId = null;
|
|
120
|
+
let rightWayId = null;
|
|
121
|
+
if (lane.props.leftBoundaryId) {
|
|
122
|
+
const leftLs = shapeMap.get(lane.props.leftBoundaryId);
|
|
123
|
+
if (leftLs)
|
|
124
|
+
leftWayId = resolveOsmId(leftLs.id, leftLs.props.osmId);
|
|
125
|
+
}
|
|
126
|
+
if (lane.props.rightBoundaryId) {
|
|
127
|
+
const rightLs = shapeMap.get(lane.props.rightBoundaryId);
|
|
128
|
+
if (rightLs)
|
|
129
|
+
rightWayId = resolveOsmId(rightLs.id, rightLs.props.osmId);
|
|
130
|
+
}
|
|
131
|
+
if (original) {
|
|
132
|
+
// Preserve the original member order; only refresh left/right refs.
|
|
133
|
+
for (const m of original.members) {
|
|
134
|
+
if (m.type === 'way' && m.role === 'left' && leftWayId) {
|
|
135
|
+
members.push({ type: 'way', ref: leftWayId, role: 'left' });
|
|
136
|
+
}
|
|
137
|
+
else if (m.type === 'way' && m.role === 'right' && rightWayId) {
|
|
138
|
+
members.push({ type: 'way', ref: rightWayId, role: 'right' });
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
members.push({ ...m });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
if (leftWayId)
|
|
147
|
+
members.push({ type: 'way', ref: leftWayId, role: 'left' });
|
|
148
|
+
if (rightWayId)
|
|
149
|
+
members.push({ type: 'way', ref: rightWayId, role: 'right' });
|
|
150
|
+
}
|
|
151
|
+
const tags = original ? { ...original.tags } : {};
|
|
152
|
+
if (lane.props.attributes) {
|
|
153
|
+
for (const [k, v] of Object.entries(lane.props.attributes)) {
|
|
154
|
+
if (v !== undefined && v !== null && v !== '')
|
|
155
|
+
tags[k] = String(v);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (!tags.type)
|
|
159
|
+
tags.type = 'lanelet';
|
|
160
|
+
relationsOut.push({ id: osmId, members, tags });
|
|
161
|
+
}
|
|
162
|
+
return { nodes: nodesOut, ways: waysOut, relations: relationsOut, shapeNodeOsmIds, shapeWayOsmIds, shapeRelationOsmIds };
|
|
163
|
+
}
|
|
164
|
+
function nodeToXml(node) {
|
|
165
|
+
const tags = [];
|
|
166
|
+
// `ele` is emitted as a tag (Lanelet2 OSM convention).
|
|
167
|
+
const tagEntries = Object.entries(node.tags).filter(([k]) => k !== 'ele');
|
|
168
|
+
if (node.ele !== undefined) {
|
|
169
|
+
tags.push(` <tag k='ele' v='${escapeXml(formatEle(node.ele))}' />`);
|
|
170
|
+
}
|
|
171
|
+
else if (node.tags.ele !== undefined) {
|
|
172
|
+
tags.push(` <tag k='ele' v='${escapeXml(node.tags.ele)}' />`);
|
|
173
|
+
}
|
|
174
|
+
for (const [k, v] of tagEntries) {
|
|
175
|
+
tags.push(` <tag k='${escapeXml(k)}' v='${escapeXml(v)}' />`);
|
|
176
|
+
}
|
|
177
|
+
if (tags.length === 0) {
|
|
178
|
+
return ` <node id='${escapeXml(node.id)}' visible='true' version='1' lat='${formatLatLon(node.lat)}' lon='${formatLatLon(node.lon)}' />\n`;
|
|
179
|
+
}
|
|
180
|
+
let xml = ` <node id='${escapeXml(node.id)}' visible='true' version='1' lat='${formatLatLon(node.lat)}' lon='${formatLatLon(node.lon)}'>\n`;
|
|
181
|
+
xml += tags.join('\n') + '\n';
|
|
182
|
+
xml += ` </node>\n`;
|
|
183
|
+
return xml;
|
|
184
|
+
}
|
|
185
|
+
function wayToXml(way) {
|
|
186
|
+
let xml = ` <way id='${escapeXml(way.id)}' visible='true' version='1'>\n`;
|
|
187
|
+
for (const ref of way.nodeRefs) {
|
|
188
|
+
xml += ` <nd ref='${escapeXml(ref)}' />\n`;
|
|
189
|
+
}
|
|
190
|
+
for (const [k, v] of Object.entries(way.tags)) {
|
|
191
|
+
xml += ` <tag k='${escapeXml(k)}' v='${escapeXml(v)}' />\n`;
|
|
192
|
+
}
|
|
193
|
+
xml += ` </way>\n`;
|
|
194
|
+
return xml;
|
|
195
|
+
}
|
|
196
|
+
function relationToXml(rel) {
|
|
197
|
+
let xml = ` <relation id='${escapeXml(rel.id)}' visible='true' version='1'>\n`;
|
|
198
|
+
for (const m of rel.members) {
|
|
199
|
+
xml += ` <member type='${escapeXml(m.type)}' ref='${escapeXml(m.ref)}' role='${escapeXml(m.role)}' />\n`;
|
|
200
|
+
}
|
|
201
|
+
for (const [k, v] of Object.entries(rel.tags)) {
|
|
202
|
+
xml += ` <tag k='${escapeXml(k)}' v='${escapeXml(v)}' />\n`;
|
|
203
|
+
}
|
|
204
|
+
xml += ` </relation>\n`;
|
|
205
|
+
return xml;
|
|
206
|
+
}
|
|
207
|
+
/** Build a Lanelet2 OSM XML document from a snapshot. */
|
|
208
|
+
export function exportToLanelet2(snapshot, options = {}) {
|
|
209
|
+
const { sidecar = null, mapOrigin = null } = options;
|
|
210
|
+
// Sidecar wins (= round-trip from import). Otherwise fall back to the
|
|
211
|
+
// current map origin, then DEFAULT.
|
|
212
|
+
let originLat;
|
|
213
|
+
let originLon;
|
|
214
|
+
if (sidecar) {
|
|
215
|
+
originLat = sidecar.originLat;
|
|
216
|
+
originLon = sidecar.originLon;
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
originLat = mapOrigin?.lat ?? DEFAULT_ORIGIN_LAT;
|
|
220
|
+
originLon = mapOrigin?.lon ?? DEFAULT_ORIGIN_LON;
|
|
221
|
+
}
|
|
222
|
+
const sidecarData = sidecar ? parseOsmXml(sidecar.rawXml) : null;
|
|
223
|
+
const fromShapes = buildFromShapes(snapshot.shapes, originLat, originLon, sidecarData);
|
|
224
|
+
// Combine: sidecar entries (skipping IDs that shapes overwrite) + shape-derived entries.
|
|
225
|
+
// Sidecar order is preserved to keep round-trips stable.
|
|
226
|
+
const finalNodes = [];
|
|
227
|
+
const finalWays = [];
|
|
228
|
+
const finalRelations = [];
|
|
229
|
+
if (sidecarData) {
|
|
230
|
+
sidecarData.nodes.forEach((n, id) => {
|
|
231
|
+
if (fromShapes.shapeNodeOsmIds.has(id))
|
|
232
|
+
return;
|
|
233
|
+
finalNodes.push({ id, lat: n.lat, lon: n.lon, ele: n.ele, tags: n.tags });
|
|
234
|
+
});
|
|
235
|
+
sidecarData.ways.forEach((w, id) => {
|
|
236
|
+
if (fromShapes.shapeWayOsmIds.has(id))
|
|
237
|
+
return;
|
|
238
|
+
finalWays.push({ id, nodeRefs: w.nodeRefs, tags: w.tags });
|
|
239
|
+
});
|
|
240
|
+
for (const r of sidecarData.relations) {
|
|
241
|
+
if (fromShapes.shapeRelationOsmIds.has(r.id))
|
|
242
|
+
continue;
|
|
243
|
+
finalRelations.push({ id: r.id, members: r.members.map(m => ({ ...m })), tags: r.tags });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
fromShapes.nodes.forEach(n => finalNodes.push(n));
|
|
247
|
+
fromShapes.ways.forEach(w => finalWays.push(w));
|
|
248
|
+
for (const r of fromShapes.relations)
|
|
249
|
+
finalRelations.push(r);
|
|
250
|
+
let xml = `<?xml version='1.0' encoding='UTF-8'?>\n`;
|
|
251
|
+
xml += `<osm version='0.6' generator='drawtonomy' drawtonomy_origin_lat='${formatLatLon(originLat)}' drawtonomy_origin_lon='${formatLatLon(originLon)}'>\n`;
|
|
252
|
+
for (const n of finalNodes)
|
|
253
|
+
xml += nodeToXml(n);
|
|
254
|
+
for (const w of finalWays)
|
|
255
|
+
xml += wayToXml(w);
|
|
256
|
+
for (const r of finalRelations)
|
|
257
|
+
xml += relationToXml(r);
|
|
258
|
+
xml += `</osm>\n`;
|
|
259
|
+
return xml;
|
|
260
|
+
}
|
|
261
|
+
//# sourceMappingURL=lanelet2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lanelet2.js","sourceRoot":"","sources":["../../src/exporter/lanelet2.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opendrive.d.ts","sourceRoot":"","sources":["../../src/exporter/opendrive.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAGV,kBAAkB,EAMnB,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"opendrive.d.ts","sourceRoot":"","sources":["../../src/exporter/opendrive.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAGV,kBAAkB,EAMnB,MAAM,UAAU,CAAA;AAilBjB;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,MAAM,CAuEtE"}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// - Lane connectivity (next/prev) is written into <link>
|
|
9
9
|
// - Coordinate frame: canvas (x right, y down) → ENU (x right, y up); y is flipped
|
|
10
10
|
import { computeCenterlineWithWidth } from './laneCenterline';
|
|
11
|
+
import { originToProjString } from './projection';
|
|
11
12
|
import { escapeXml, fmt, pxToEnuX, pxToEnuY, pxToMeter } from './units';
|
|
12
13
|
/** O(1) shape lookup by id. */
|
|
13
14
|
function buildShapeMap(shapes) {
|
|
@@ -538,10 +539,21 @@ export function exportToOpenDrive(snapshot) {
|
|
|
538
539
|
const laneIdToRoadId = new Map();
|
|
539
540
|
lanes.forEach((lane, i) => laneIdToRoadId.set(lane.id, i + 1));
|
|
540
541
|
const dateStr = new Date().toISOString();
|
|
542
|
+
const bbox = computeEnuBoundingBox(shapeMap);
|
|
543
|
+
const geoRefProj = originToProjString(snapshot.origin);
|
|
541
544
|
const lines = [];
|
|
542
545
|
lines.push(`<?xml version="1.0" encoding="UTF-8"?>`);
|
|
543
546
|
lines.push(`<OpenDRIVE>`);
|
|
544
|
-
|
|
547
|
+
// OpenDRIVE 1.8 expects <geoReference> inside <header>. We always emit one —
|
|
548
|
+
// tmerc-at-origin when snapshot.origin is set, WGS84 longlat as a fallback —
|
|
549
|
+
// so downstream tools (esmini, RoadRunner, asam-qc-opendrive) see a defined
|
|
550
|
+
// coordinate reference system rather than nothing. The N/S/E/W attributes
|
|
551
|
+
// are populated from the actual point cloud so the header bbox reflects the
|
|
552
|
+
// map extent in ENU metres.
|
|
553
|
+
lines.push(` <header revMajor="1" revMinor="8" name="drawtonomy" version="1.0" date="${dateStr}" ` +
|
|
554
|
+
`north="${fmt(bbox.north)}" south="${fmt(bbox.south)}" east="${fmt(bbox.east)}" west="${fmt(bbox.west)}" vendor="drawtonomy">`);
|
|
555
|
+
lines.push(` <geoReference><![CDATA[${escapeCdata(geoRefProj)}]]></geoReference>`);
|
|
556
|
+
lines.push(` </header>`);
|
|
545
557
|
const pointOverrides = buildBoundaryAlignmentOverrides(shapeMap, lanes);
|
|
546
558
|
const laneToGeom = new Map();
|
|
547
559
|
for (const lane of lanes) {
|
|
@@ -562,4 +574,45 @@ export function exportToOpenDrive(snapshot) {
|
|
|
562
574
|
lines.push(`</OpenDRIVE>`);
|
|
563
575
|
return lines.join('\n');
|
|
564
576
|
}
|
|
577
|
+
/**
|
|
578
|
+
* Compute the axis-aligned bounding box of all point shapes in ENU metres.
|
|
579
|
+
* Used to populate OpenDRIVE <header> north/south/east/west attributes.
|
|
580
|
+
* Returns zeros when the snapshot has no points.
|
|
581
|
+
*/
|
|
582
|
+
function computeEnuBoundingBox(shapeMap) {
|
|
583
|
+
let minX = Infinity;
|
|
584
|
+
let maxX = -Infinity;
|
|
585
|
+
let minY = Infinity;
|
|
586
|
+
let maxY = -Infinity;
|
|
587
|
+
for (const s of shapeMap.values()) {
|
|
588
|
+
if (s.type !== 'point')
|
|
589
|
+
continue;
|
|
590
|
+
if (s.x < minX)
|
|
591
|
+
minX = s.x;
|
|
592
|
+
if (s.x > maxX)
|
|
593
|
+
maxX = s.x;
|
|
594
|
+
if (s.y < minY)
|
|
595
|
+
minY = s.y;
|
|
596
|
+
if (s.y > maxY)
|
|
597
|
+
maxY = s.y;
|
|
598
|
+
}
|
|
599
|
+
if (!Number.isFinite(minX)) {
|
|
600
|
+
return { north: 0, south: 0, east: 0, west: 0 };
|
|
601
|
+
}
|
|
602
|
+
// Canvas y points down, ENU y points up — flip when reporting bounds.
|
|
603
|
+
return {
|
|
604
|
+
west: pxToEnuX(minX),
|
|
605
|
+
east: pxToEnuX(maxX),
|
|
606
|
+
south: pxToEnuY(maxY),
|
|
607
|
+
north: pxToEnuY(minY),
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Escape a string so it can appear safely inside an XML CDATA section. The
|
|
612
|
+
* only character sequence that ends a CDATA section is `]]>`, so we split it
|
|
613
|
+
* across two CDATA sections.
|
|
614
|
+
*/
|
|
615
|
+
function escapeCdata(s) {
|
|
616
|
+
return s.replace(/]]>/g, ']]]]><![CDATA[>');
|
|
617
|
+
}
|
|
565
618
|
//# sourceMappingURL=opendrive.js.map
|