@@ -2,7 +2,8 @@ import { useBitmapTileLayer } from "@/hooks/map"
2
2
import { useOsmFile , useOsmWorker } from "@/hooks/osm"
3
3
import { DEFAULT_BASE_PBF_URL , DEFAULT_PATCH_PBF_URL } from "@/settings"
4
4
import { ArrowLeft , ArrowRight , Loader2Icon } from "lucide-react"
5
- import type { OsmChanges } from "osm.ts"
5
+ import { Osm , writeOsmToPbfStream , type OsmChanges } from "osm.ts"
6
+ import { showSaveFilePicker } from "native-file-system-adapter"
6
7
import { useCallback , useEffect , useRef , useState , useTransition } from "react"
7
8
import Basemap from "./basemap"
8
9
import DeckGlOverlay from "./deckgl-overlay"
@@ -11,18 +12,25 @@ import LogContent from "./log"
11
12
import OsmInfoTable from "./osm-info-table"
12
13
import OsmPbfFileInput from "./osm-pbf-file-input"
13
14
import { Button } from "./ui/button"
15
+ import useStartTask from "@/hooks/log"
14
16
15
17
export default function Merge ( ) {
16
18
const [ baseFile , setBaseFile ] = useState < File | null > ( null )
17
19
const [ patchFile , setPatchFile ] = useState < File | null > ( null )
18
- const [ baseOsm , baseOsmIsLoading ] = useOsmFile ( baseFile , "base" )
19
- const [ patchOsm , patchOsmIsLoading ] = useOsmFile ( patchFile , "patch" )
20
+ const [ baseOsm , setBaseOsm , baseOsmIsLoading ] = useOsmFile ( baseFile , "base" )
21
+ const [ patchOsm , setPatchOsm , patchOsmIsLoading ] = useOsmFile (
22
+ patchFile ,
23
+ "patch" ,
24
+ )
25
+ const [ mergedOsm , setMergedOsm ] = useState < Osm | null > ( null )
20
26
const osmWorker = useOsmWorker ( )
21
27
const [ isTransitioning , startTransition ] = useTransition ( )
22
28
const [ changes , setChanges ] = useState < OsmChanges | null > ( null )
29
+ const startTask = useStartTask ( )
23
30
24
31
const baseTileLayer = useBitmapTileLayer ( baseOsm )
25
32
const patchTileLayer = useBitmapTileLayer ( patchOsm )
33
+ const mergedTileLayer = useBitmapTileLayer ( mergedOsm )
26
34
27
35
const [ step , setStep ] = useState < number > ( 1 )
28
36
@@ -60,6 +68,30 @@ export default function Merge() {
60
68
setStep ( ( s ) => s + 1 )
61
69
} , [ ] )
62
70
71
+ const downloadOsm = useCallback (
72
+ async ( osm : Osm , name ?: string ) => {
73
+ startTransition ( async ( ) => {
74
+ const task = startTask ( "Generating OSM file to download" , "info" )
75
+ const suggestedName =
76
+ name ?? ( osm . id . endsWith ( ".pbf" ) ? osm . id : `${ osm . id } .pbf` )
77
+ const fileHandle = await showSaveFilePicker ( {
78
+ suggestedName,
79
+ types : [
80
+ {
81
+ description : "OSM PBF" ,
82
+ accept : { "application/x-protobuf" : [ ".pbf" ] } ,
83
+ } ,
84
+ ] ,
85
+ } )
86
+ const stream = await fileHandle . createWritable ( )
87
+ await writeOsmToPbfStream ( osm , stream )
88
+ await stream . close ( )
89
+ task . end ( `Created ${ fileHandle . name } PBF for download` , "ready" )
90
+ } )
91
+ } ,
92
+ [ startTask ] ,
93
+ )
94
+
63
95
return (
64
96
< Main >
65
97
< Sidebar >
@@ -199,7 +231,7 @@ export default function Merge() {
199
231
</ If >
200
232
201
233
< If t = { step === 3 } >
202
- { isTransitioning ? (
234
+ { isTransitioning || changes == null ? (
203
235
< div className = "flex items-center gap-1" >
204
236
< Loader2Icon className = "animate-spin size-4" />
205
237
< div className = "font-bold" > GENERATING CHANGESET</ div >
@@ -210,34 +242,39 @@ export default function Merge() {
210
242
< div >
211
243
Changes have been generated and can be reviewed below. Once
212
244
the review is complete you can apply changes to generate a new
213
- OSM file ready to be downloaded. You cannot go back from the
214
- next step.
245
+ OSM file ready to be downloaded.
215
246
</ div >
216
247
</ >
217
248
) }
218
249
< div className = "border border-slate-950 p-1 max-h-96" >
219
250
{ changes && < ChangesSummary changes = { changes } /> }
220
251
</ div >
221
252
< Button
222
- variant = "outline"
253
+ disabled = { changes == null || isTransitioning }
223
254
onClick = { ( ) => {
224
255
if ( ! osmWorker ) throw Error ( "No OSM worker" )
225
256
nextStep ( )
226
257
startTransition ( async ( ) => {
227
258
if ( ! changes ) throw Error ( "No changes to apply" )
228
- // await osmWorker.applyChangeset()
259
+ if ( ! baseOsm ) throw Error ( "No base OSM" )
260
+ const newOsm = await osmWorker . applyChanges (
261
+ `merged-${ baseOsm . id } ` ,
262
+ )
263
+ setBaseOsm ( null )
264
+ setPatchOsm ( null )
265
+ setMergedOsm ( Osm . from ( newOsm ) )
229
266
} )
230
267
} }
231
268
>
232
- < ArrowRight /> Apply changes
269
+ < ArrowRight /> Apply changes (irreversible)
233
270
</ Button >
234
271
< Button variant = "outline" onClick = { prevStep } >
235
272
< ArrowLeft /> Back
236
273
</ Button >
237
274
</ If >
238
275
239
276
< If t = { step === 4 } >
240
- { isTransitioning ? (
277
+ { isTransitioning || mergedOsm == null ? (
241
278
< div className = "flex items-center gap-1" >
242
279
< Loader2Icon className = "animate-spin size-4" />
243
280
< div className = "font-bold" > APPLYING CHANGES</ div >
@@ -249,6 +286,9 @@ export default function Merge() {
249
286
Changes have been applied and a new OSM dataset has been
250
287
created. It can be inspected here and downloaded as a new PBF.
251
288
</ div >
289
+ < Button onClick = { ( ) => downloadOsm ( mergedOsm ) } >
290
+ Download merged OSM PBF
291
+ </ Button >
252
292
{ /* INSERT OSM VIEW CODE HERE */ }
253
293
</ >
254
294
) }
@@ -257,7 +297,9 @@ export default function Merge() {
257
297
</ Sidebar >
258
298
< MapContent >
259
299
< Basemap >
260
- < DeckGlOverlay layers = { [ baseTileLayer , patchTileLayer ] } />
300
+ < DeckGlOverlay
301
+ layers = { [ baseTileLayer , patchTileLayer , mergedTileLayer ] }
302
+ />
261
303
</ Basemap >
262
304
</ MapContent >
263
305
</ Main >
0 commit comments