33
44async function exportToSvg ( ) {
55 TIME && console . time ( "exportToSvg" ) ;
6- const url = await getMapURL ( "svg" , { fullMap : true } ) ;
7- const link = document . createElement ( "a" ) ;
8- link . download = getFileName ( ) + ".svg" ;
9- link . href = url ;
10- link . click ( ) ;
11-
12- const message = `${ link . download } is saved. Open 'Downloads' screen (ctrl + J) to check` ;
13- tip ( message , true , "success" , 5000 ) ;
14- TIME && console . timeEnd ( "exportToSvg" ) ;
6+ try {
7+ const url = await getMapURL ( "svg" , { fullMap : true } ) ;
8+ const link = document . createElement ( "a" ) ;
9+ link . download = getFileName ( ) + ".svg" ;
10+ link . href = url ;
11+ link . click ( ) ;
12+
13+ const message = `${ link . download } is saved. Open 'Downloads' screen (CTRL + J) to check` ;
14+ tip ( message , true , "success" , 5000 ) ;
15+ } catch ( error ) {
16+ ERROR && console . error ( error ) ;
17+ tip ( `SVG export failed: ${ error ?. message || "Unknown error" } ` , true , "error" , 5000 ) ;
18+ } finally {
19+ TIME && console . timeEnd ( "exportToSvg" ) ;
20+ }
1521}
1622
1723async function exportToPng ( ) {
1824 TIME && console . time ( "exportToPng" ) ;
19- const url = await getMapURL ( "png" ) ;
20-
21- const link = document . createElement ( "a" ) ;
22- const canvas = document . createElement ( "canvas" ) ;
23- const ctx = canvas . getContext ( "2d" ) ;
24- canvas . width = svgWidth * pngResolutionInput . value ;
25- canvas . height = svgHeight * pngResolutionInput . value ;
26- const img = new Image ( ) ;
27- img . src = url ;
28-
29- img . onload = function ( ) {
30- ctx . drawImage ( img , 0 , 0 , canvas . width , canvas . height ) ;
31- link . download = getFileName ( ) + ".png" ;
32- canvas . toBlob ( function ( blob ) {
33- link . href = window . URL . createObjectURL ( blob ) ;
34- link . click ( ) ;
35- window . setTimeout ( function ( ) {
36- canvas . remove ( ) ;
37- window . URL . revokeObjectURL ( link . href ) ;
38-
39- const message = `${ link . download } is saved. Open 'Downloads' screen (ctrl + J) to check. You can set image scale in options` ;
40- tip ( message , true , "success" , 5000 ) ;
41- } , 1000 ) ;
25+ try {
26+ const url = await getMapURL ( "png" ) ;
27+ const link = document . createElement ( "a" ) ;
28+ const canvas = document . createElement ( "canvas" ) ;
29+ const ctx = canvas . getContext ( "2d" ) ;
30+ canvas . width = svgWidth * pngResolutionInput . value ;
31+ canvas . height = svgHeight * pngResolutionInput . value ;
32+
33+ const blob = await new Promise ( ( resolve , reject ) => {
34+ const img = new Image ( ) ;
35+ img . onload = function ( ) {
36+ ctx . drawImage ( img , 0 , 0 , canvas . width , canvas . height ) ;
37+ canvas . toBlob ( blob => {
38+ if ( ! blob ) return reject ( new Error ( "Cannot render PNG image" ) ) ;
39+ resolve ( blob ) ;
40+ } , "image/png" ) ;
41+ } ;
42+ img . onerror = ( ) => reject ( new Error ( "Cannot load map image for PNG export" ) ) ;
43+ img . src = url ;
4244 } ) ;
43- } ;
4445
45- TIME && console . timeEnd ( "exportToPng" ) ;
46+ link . download = getFileName ( ) + ".png" ;
47+ link . href = window . URL . createObjectURL ( blob ) ;
48+ link . click ( ) ;
49+ window . setTimeout ( function ( ) {
50+ canvas . remove ( ) ;
51+ window . URL . revokeObjectURL ( link . href ) ;
52+ } , 1000 ) ;
53+
54+ const message = `${ link . download } is saved. Open 'Downloads' screen (CTRL + J) to check. You can set image scale in options` ;
55+ tip ( message , true , "success" , 5000 ) ;
56+ } catch ( error ) {
57+ ERROR && console . error ( error ) ;
58+ tip ( `PNG export failed: ${ error ?. message || "Unknown error" } ` , true , "error" , 5000 ) ;
59+ } finally {
60+ TIME && console . timeEnd ( "exportToPng" ) ;
61+ }
4662}
4763
4864async function exportToJpeg ( ) {
4965 TIME && console . time ( "exportToJpeg" ) ;
50- const url = await getMapURL ( "png" ) ;
66+ try {
67+ const url = await getMapURL ( "png" ) ;
68+ const canvas = document . createElement ( "canvas" ) ;
69+ const ctx = canvas . getContext ( "2d" ) ;
70+ canvas . width = svgWidth * pngResolutionInput . value ;
71+ canvas . height = svgHeight * pngResolutionInput . value ;
5172
52- const canvas = document . createElement ( "canvas" ) ;
53- canvas . width = svgWidth * pngResolutionInput . value ;
54- canvas . height = svgHeight * pngResolutionInput . value ;
55- const img = new Image ( ) ;
56- img . src = url ;
57-
58- img . onload = async function ( ) {
59- canvas . getContext ( "2d" ) . drawImage ( img , 0 , 0 , canvas . width , canvas . height ) ;
6073 const quality = Math . min ( rn ( 1 - pngResolutionInput . value / 20 , 2 ) , 0.92 ) ;
61- const URL = await canvas . toDataURL ( "image/jpeg" , quality ) ;
74+ const blob = await new Promise ( ( resolve , reject ) => {
75+ const img = new Image ( ) ;
76+ img . onload = function ( ) {
77+ ctx . drawImage ( img , 0 , 0 , canvas . width , canvas . height ) ;
78+ canvas . toBlob (
79+ blob => {
80+ if ( ! blob ) return reject ( new Error ( "Cannot render JPEG image" ) ) ;
81+ resolve ( blob ) ;
82+ } ,
83+ "image/jpeg" ,
84+ quality
85+ ) ;
86+ } ;
87+ img . onerror = ( ) => reject ( new Error ( "Cannot load map image for JPEG export" ) ) ;
88+ img . src = url ;
89+ } ) ;
90+
6291 const link = document . createElement ( "a" ) ;
6392 link . download = getFileName ( ) + ".jpeg" ;
64- link . href = URL ;
93+ link . href = window . URL . createObjectURL ( blob ) ;
6594 link . click ( ) ;
6695 tip ( `${ link . download } is saved. Open "Downloads" screen (CTRL + J) to check` , true , "success" , 7000 ) ;
67- window . setTimeout ( ( ) => window . URL . revokeObjectURL ( URL ) , 5000 ) ;
68- } ;
69-
70- TIME && console . timeEnd ( "exportToJpeg" ) ;
96+ window . setTimeout ( ( ) => window . URL . revokeObjectURL ( link . href ) , 5000 ) ;
97+ } catch ( error ) {
98+ ERROR && console . error ( error ) ;
99+ tip ( `JPEG export failed: ${ error ?. message || "Unknown error" } ` , true , "error" , 5000 ) ;
100+ } finally {
101+ TIME && console . timeEnd ( "exportToJpeg" ) ;
102+ }
71103}
72104
73105async function exportToPngTiles ( ) {
@@ -132,17 +164,24 @@ async function exportToPngTiles() {
132164 }
133165
134166 status . innerHTML = "Zipping files..." ;
135- zip . generateAsync ( { type : "blob" } ) . then ( blob => {
136- status . innerHTML = "Downloading the archive..." ;
137- const link = document . createElement ( "a" ) ;
138- link . href = URL . createObjectURL ( blob ) ;
139- link . download = getFileName ( ) + ".zip" ;
140- link . click ( ) ;
141- link . remove ( ) ;
142-
143- status . innerHTML = 'Done. Check .zip file in "Downloads" (crtl + J)' ;
144- setTimeout ( ( ) => URL . revokeObjectURL ( link . href ) , 5000 ) ;
145- } ) ;
167+ zip
168+ . generateAsync ( { type : "blob" } )
169+ . then ( blob => {
170+ status . innerHTML = "Downloading the archive..." ;
171+ const link = document . createElement ( "a" ) ;
172+ link . href = URL . createObjectURL ( blob ) ;
173+ link . download = getFileName ( ) + ".zip" ;
174+ link . click ( ) ;
175+ link . remove ( ) ;
176+
177+ status . innerHTML = 'Done. Check .zip file in "Downloads" (CTRL + J)' ;
178+ setTimeout ( ( ) => URL . revokeObjectURL ( link . href ) , 5000 ) ;
179+ } )
180+ . catch ( error => {
181+ ERROR && console . error ( error ) ;
182+ status . innerHTML = "Tiles export failed" ;
183+ tip ( `PNG tiles export failed: ${ error ?. message || "Unknown error" } ` , true , "error" , 5000 ) ;
184+ } ) ;
146185
147186 // promisified img.onload
148187 function loadImage ( img ) {
@@ -583,70 +622,70 @@ function saveGeoJsonZones() {
583622 // Handles multiple disconnected components and holes properly
584623 function getZonePolygonCoordinates ( zoneCells ) {
585624 const cellsInZone = new Set ( zoneCells ) ;
586- const ofSameType = ( cellId ) => cellsInZone . has ( cellId ) ;
587- const ofDifferentType = ( cellId ) => ! cellsInZone . has ( cellId ) ;
588-
625+ const ofSameType = cellId => cellsInZone . has ( cellId ) ;
626+ const ofDifferentType = cellId => ! cellsInZone . has ( cellId ) ;
627+
589628 const checkedCells = new Set ( ) ;
590629 const rings = [ ] ; // Array of LinearRings (each ring is an array of coordinates)
591-
630+
592631 // Find all boundary components by tracing each connected region
593632 for ( const cellId of zoneCells ) {
594633 if ( checkedCells . has ( cellId ) ) continue ;
595-
634+
596635 // Check if this cell is on the boundary (has a neighbor outside the zone)
597636 const neighbors = cells . c [ cellId ] ;
598637 const onBorder = neighbors . some ( ofDifferentType ) ;
599638 if ( ! onBorder ) continue ;
600-
639+
601640 // Check if this is an inner lake (hole) - skip if so
602641 const feature = pack . features [ cells . f [ cellId ] ] ;
603642 if ( feature . type === "lake" && feature . shoreline ) {
604643 if ( feature . shoreline . every ( ofSameType ) ) continue ;
605644 }
606-
645+
607646 // Find a starting vertex that's on the boundary
608647 const cellVertices = cells . v [ cellId ] ;
609648 let startingVertex = null ;
610-
649+
611650 for ( const vertexId of cellVertices ) {
612651 const vertexCells = vertices . c [ vertexId ] ;
613652 if ( vertexCells . some ( ofDifferentType ) ) {
614653 startingVertex = vertexId ;
615654 break ;
616655 }
617656 }
618-
657+
619658 if ( startingVertex === null ) continue ;
620-
659+
621660 // Use connectVertices to trace the boundary (reusing existing logic)
622661 const vertexChain = connectVertices ( {
623662 vertices,
624663 startingVertex,
625664 ofSameType,
626- addToChecked : ( cellId ) => checkedCells . add ( cellId ) ,
627- closeRing : false , // We'll close it manually after converting to coordinates
665+ addToChecked : cellId => checkedCells . add ( cellId ) ,
666+ closeRing : false // We'll close it manually after converting to coordinates
628667 } ) ;
629-
668+
630669 if ( vertexChain . length < 3 ) continue ;
631-
670+
632671 // Convert vertex chain to coordinates
633672 const coordinates = [ ] ;
634673 for ( const vertexId of vertexChain ) {
635674 const [ x , y ] = vertices . p [ vertexId ] ;
636675 coordinates . push ( getCoordinates ( x , y , 4 ) ) ;
637676 }
638-
677+
639678 // Close the ring (first coordinate = last coordinate)
640679 if ( coordinates . length > 0 ) {
641680 coordinates . push ( coordinates [ 0 ] ) ;
642681 }
643-
682+
644683 // Only add ring if it has at least 4 positions (minimum for valid LinearRing)
645684 if ( coordinates . length >= 4 ) {
646685 rings . push ( coordinates ) ;
647686 }
648687 }
649-
688+
650689 return rings ;
651690 }
652691
@@ -656,18 +695,18 @@ function saveGeoJsonZones() {
656695 if ( zone . hidden || ! zone . cells || zone . cells . length === 0 ) return ;
657696
658697 const rings = getZonePolygonCoordinates ( zone . cells ) ;
659-
698+
660699 // Skip if no valid rings were generated
661700 if ( rings . length === 0 ) return ;
662-
701+
663702 const properties = {
664703 id : zone . i ,
665704 name : zone . name ,
666705 type : zone . type ,
667706 color : zone . color ,
668707 cells : zone . cells
669708 } ;
670-
709+
671710 // If there's only one ring, use Polygon geometry
672711 if ( rings . length === 1 ) {
673712 const feature = {
0 commit comments