1- using System ;
1+ using System ;
22using System . Collections . Generic ;
33using System . IO ;
44using System . Linq ;
5+ using System . Net . Http ;
56using Resgrid . Model ;
67using Resgrid . Model . Providers ;
78using SharpKml . Dom ;
@@ -15,38 +16,143 @@ public List<Coordinates> ImportFile(Stream input, bool isKmz)
1516 {
1617 var coordinates = new List < Coordinates > ( ) ;
1718
19+ if ( input == null )
20+ return coordinates ;
21+
1822 try
1923 {
2024 KmlFile file ;
2125 if ( isKmz )
2226 {
2327 var kmz = KmzFile . Open ( input ) ;
24- file = kmz . GetDefaultKmlFile ( ) ;
28+ file = kmz ? . GetDefaultKmlFile ( ) ;
2529 }
2630 else
2731 file = KmlFile . Load ( input ) ;
2832
29-
30- Kml kml = file . Root as Kml ;
31- if ( kml != null )
33+ if ( file ? . Root is Kml kml )
3234 {
33- foreach ( var placemark in kml . Flatten ( ) . OfType < Placemark > ( ) )
34- {
35- var coords = new Coordinates ( ) ;
36- coords . Name = placemark . Name ;
37- coords . Latitude = placemark . CalculateBounds ( ) . Center . Latitude ;
38- coords . Longitude = placemark . CalculateBounds ( ) . Center . Longitude ;
35+ ExtractCoordinates ( kml , coordinates ) ;
3936
40- coordinates . Add ( coords ) ;
37+ // Resolve NetworkLinks to external KML/KMZ resources
38+ var networkLinks = kml . Flatten ( ) . OfType < NetworkLink > ( ) . ToList ( ) ;
39+ foreach ( var networkLink in networkLinks )
40+ {
41+ try
42+ {
43+ ResolveNetworkLink ( networkLink , coordinates ) ;
44+ }
45+ catch
46+ {
47+ // Skip failed network link resolution
48+ }
4149 }
4250 }
4351 }
44- catch
52+ catch ( Exception ex )
4553 {
46-
54+ System . Diagnostics . Debug . WriteLine ( $ "KmlProvider.ImportFile error: { ex . Message } " ) ;
4755 }
48-
56+
4957 return coordinates ;
5058 }
59+
60+ private static void ExtractCoordinates ( Kml kml , List < Coordinates > coordinates )
61+ {
62+ foreach ( var placemark in kml . Flatten ( ) . OfType < Placemark > ( ) )
63+ {
64+ var coords = new Coordinates ( ) ;
65+ coords . Name = placemark . Name ;
66+
67+ try
68+ {
69+ var bounds = placemark . CalculateBounds ( ) ;
70+ if ( bounds != null )
71+ {
72+ coords . Latitude = bounds . Center . Latitude ;
73+ coords . Longitude = bounds . Center . Longitude ;
74+ }
75+ }
76+ catch
77+ {
78+ // Skip placemarks that fail bounds calculation
79+ }
80+
81+ if ( coords . Latitude . HasValue && coords . Longitude . HasValue )
82+ coordinates . Add ( coords ) ;
83+ }
84+ }
85+
86+ private static void ResolveNetworkLink ( NetworkLink networkLink , List < Coordinates > coordinates )
87+ {
88+ if ( networkLink ? . Link ? . Href == null )
89+ return ;
90+
91+ var href = networkLink . Link . Href . OriginalString ;
92+ if ( string . IsNullOrWhiteSpace ( href ) )
93+ return ;
94+
95+ // Step 1: URI-based detection — check AbsolutePath for .kmz/.kml suffix
96+ bool ? isKmzFromUri = null ;
97+ if ( Uri . TryCreate ( href , UriKind . Absolute , out Uri uri ) )
98+ {
99+ var path = uri . AbsolutePath ;
100+ if ( path . EndsWith ( ".kmz" , StringComparison . OrdinalIgnoreCase ) )
101+ isKmzFromUri = true ;
102+ else if ( path . EndsWith ( ".kml" , StringComparison . OrdinalIgnoreCase ) )
103+ isKmzFromUri = false ;
104+ }
105+
106+ using ( var httpClient = new HttpClient { Timeout = System . TimeSpan . FromSeconds ( 30 ) } )
107+ using ( var response = httpClient . GetAsync ( href ) . GetAwaiter ( ) . GetResult ( ) )
108+ {
109+ if ( ! response . IsSuccessStatusCode )
110+ return ;
111+
112+ // Step 2: Content-Type detection — fallback when URI is inconclusive
113+ bool ? isKmzFromContentType = null ;
114+ var contentType = response . Content . Headers . ContentType ? . MediaType ;
115+ if ( string . Equals ( contentType , "application/vnd.google-earth.kmz" , StringComparison . OrdinalIgnoreCase ) )
116+ isKmzFromContentType = true ;
117+ else if ( string . Equals ( contentType , "application/vnd.google-earth.kml+xml" , StringComparison . OrdinalIgnoreCase ) )
118+ isKmzFromContentType = false ;
119+
120+ // Download into memory so we can peek bytes and re-read for parsing
121+ var contentBytes = response . Content . ReadAsByteArrayAsync ( ) . GetAwaiter ( ) . GetResult ( ) ;
122+
123+ // Step 3: ZIP magic-byte detection — final fallback
124+ bool ? isKmzFromMagic = null ;
125+ if ( contentBytes . Length >= 4 )
126+ {
127+ // PK\x03\x04 is the ZIP magic number
128+ isKmzFromMagic = contentBytes [ 0 ] == 0x50 && contentBytes [ 1 ] == 0x4B
129+ && contentBytes [ 2 ] == 0x03 && contentBytes [ 3 ] == 0x04 ;
130+ }
131+
132+ // Precedence: URI suffix > Content-Type header > magic bytes; default to false
133+ bool isKmz = isKmzFromUri ?? isKmzFromContentType ?? isKmzFromMagic ?? false ;
134+
135+ using ( var ms = new MemoryStream ( contentBytes ) )
136+ {
137+ KmlFile file ;
138+ if ( isKmz )
139+ {
140+ var kmz = KmzFile . Open ( ms ) ;
141+ file = kmz ? . GetDefaultKmlFile ( ) ;
142+ if ( file == null )
143+ return ;
144+ }
145+ else
146+ {
147+ file = KmlFile . Load ( ms ) ;
148+ }
149+
150+ if ( file ? . Root is Kml kml )
151+ {
152+ ExtractCoordinates ( kml , coordinates ) ;
153+ }
154+ }
155+ }
156+ }
51157 }
52158}
0 commit comments