@@ -57,13 +57,17 @@ export default function pbfToArcGIS(
5757 keyName : getKeyName ( field )
5858 } ) ) ;
5959 const geometryParser = getGeometryParser ( geometryType ) ;
60+ const hasZ = featureResult . hasZ === true ;
61+ const hasM = featureResult . hasM === true ;
6062
6163 // Normalize Features
6264 out . features = featureResult . features . map (
6365 ( f : any ) =>
6466 ( {
6567 attributes : collectAttributes ( attributeFields , f . attributes ) ,
66- geometry : ( ( f . geometry && geometryParser ( f , transform ) ) as any ) || null
68+ geometry :
69+ ( ( f . geometry && geometryParser ( f , transform , hasZ , hasM ) ) as any ) ||
70+ null
6771 } as IFeature )
6872 ) ;
6973
@@ -218,13 +222,23 @@ function getGeometryParser(featureType: number) {
218222 case 3 :
219223 return createPolygon ;
220224 case 2 :
221- return createLine ;
225+ return createPolyLine ;
222226 case 0 :
223227 return createPoint ;
224228 /* istanbul ignore next --@preserve */
225229 default :
226- return createPolygon ;
230+ throw new ArcGISRequestError ( "Geometry type not supported." , 501 ) ;
231+ }
232+ }
233+
234+ function getCoordinateDimensions ( hasZ : boolean , hasM : boolean ) {
235+ if ( hasZ && hasM ) {
236+ return 4 ;
237+ }
238+ if ( hasZ || hasM ) {
239+ return 3 ;
227240 }
241+ return 2 ;
228242}
229243
230244function getGeometryType ( featureType : number ) : GeometryType {
@@ -241,183 +255,184 @@ function getGeometryType(featureType: number): GeometryType {
241255 }
242256}
243257
244- function createPoint ( f : any , transform : any ) {
245- const p = {
246- type : "Point" ,
247- coordinates : transformTuple ( f . geometry . coords , transform )
248- } ;
249- const ret = {
250- x : p . coordinates [ 0 ] ,
251- y : p . coordinates [ 1 ]
258+ function createPoint ( f : any , transform : any , hasZ : boolean , hasM : boolean ) {
259+ const dimensions = getCoordinateDimensions ( hasZ , hasM ) ;
260+ const coordinates = transformTuple (
261+ f . geometry . coords . slice ( 0 , dimensions ) ,
262+ transform ,
263+ hasZ ,
264+ hasM
265+ ) ;
266+
267+ const point : any = {
268+ x : coordinates [ 0 ] ,
269+ y : coordinates [ 1 ]
252270 } ;
253- // structure output according to arcgis point geometry spec
254- // istanbul ignore if else --@preserve
255- if ( p . coordinates . length > 2 ) {
256- return { ...ret , z : p . coordinates [ 2 ] } ;
271+
272+ if ( coordinates . length > 2 ) {
273+ if ( hasZ ) {
274+ point . z = coordinates [ 2 ] ;
275+ }
276+ if ( hasM ) {
277+ point . m = coordinates [ hasZ ? 3 : 2 ] ;
278+ }
257279 }
258- return ret ;
280+
281+ return point ;
259282}
260283
261- function createLine ( f : any , transform : any ) {
262- let l = null ;
263- const lengths = f . geometry . lengths . length ;
284+ function createPolyLine ( f : any , transform : any , hasZ : boolean , hasM : boolean ) {
285+ const paths = [ ] ;
286+ let startPoint = 0 ;
287+ const dimensions = getCoordinateDimensions ( hasZ , hasM ) ;
264288
265- /* istanbul ignore else if --@preserve */
266- if ( lengths === 1 ) {
267- l = {
268- type : "LineString" ,
269- coordinates : createLinearRing (
270- f . geometry . coords ,
271- transform ,
272- 0 ,
273- f . geometry . lengths [ 0 ] * 2
274- )
275- } ;
276- // structure output according to arcgis Polyline geometry spec
277- // https://developers.arcgis.com/javascript/latest/api-reference/esri-geometry-Polyline.html#paths
278- return {
279- paths : [ l . coordinates ]
280- } ;
281- } else if ( lengths > 1 ) {
282- l = {
283- type : "MultiLineString" ,
284- coordinates : [ ]
285- } ;
286- let startPoint = 0 ;
287- for ( let index = 0 ; index < lengths ; index ++ ) {
288- const stopPoint = startPoint + f . geometry . lengths [ index ] * 2 ;
289- const line = createLinearRing (
289+ for ( let i = 0 ; i < f . geometry . lengths . length ; i ++ ) {
290+ const stopPoint = startPoint + f . geometry . lengths [ i ] * dimensions ;
291+ paths . push (
292+ genericPartDecoder (
290293 f . geometry . coords ,
291294 transform ,
292295 startPoint ,
293- stopPoint
294- ) ;
295- l . coordinates . push ( line ) ;
296- startPoint = stopPoint ;
297- }
298- return {
299- paths : l . coordinates
300- } ;
296+ stopPoint ,
297+ dimensions ,
298+ hasZ ,
299+ hasM
300+ )
301+ ) ;
302+ startPoint = stopPoint ;
301303 }
304+
305+ return { paths } ;
302306}
303307
304- function createPolygon ( f : any , transform : any ) {
308+ function createPolygon ( f : any , transform : any , hasZ : boolean , hasM : boolean ) {
309+ const rings = [ ] as any [ ] ;
305310 const lengths = f . geometry . lengths . length ;
306-
307- const p = {
308- type : "Polygon" ,
309- coordinates : [ ] as any [ ]
310- } ;
311-
312- if ( lengths === 1 ) {
313- p . coordinates . push (
314- createLinearRing (
315- f . geometry . coords ,
316- transform ,
317- 0 ,
318- f . geometry . lengths [ 0 ] * 2
319- )
311+ let startPoint = 0 ;
312+ const dimensions = getCoordinateDimensions ( hasZ , hasM ) ;
313+
314+ for ( let index = 0 ; index < lengths ; index ++ ) {
315+ const stopPoint = startPoint + f . geometry . lengths [ index ] * dimensions ;
316+ const ring = genericPartDecoder (
317+ f . geometry . coords ,
318+ transform ,
319+ startPoint ,
320+ stopPoint ,
321+ dimensions ,
322+ hasZ ,
323+ hasM
320324 ) ;
321- } else {
322- p . type = "MultiPolygon" ;
323325
324- let startPoint = 0 ;
325- for ( let index = 0 ; index < lengths ; index ++ ) {
326- const stopPoint = startPoint + f . geometry . lengths [ index ] * 2 ;
327- const ring = createLinearRing (
328- f . geometry . coords ,
329- transform ,
330- startPoint ,
331- stopPoint
332- ) ;
333-
334- // Check if the ring is clockwise, if so it's an outer ring
335- // If it's counter-clockwise its a hole and so push it to the prev outer ring
336- // This is perhaps a bit naive
337- // see https://github.com/terraformer-js/terraformer/blob/master/packages/arcgis/src/geojson.js
338- // for a fuller example of doing this
339- /* istanbul ignore else if --@preserve */
340- if ( ringIsClockwise ( ring ) ) {
341- p . coordinates . push ( [ ring ] ) ;
342- } else if ( p . coordinates . length > 0 ) {
343- p . coordinates [ p . coordinates . length - 1 ] . push ( ring ) ;
344- }
345- startPoint = stopPoint ;
326+ /* istanbul ignore else --@preserve */
327+ if ( ring . length > 0 ) {
328+ rings . push ( closeRing ( ring ) ) ;
346329 }
347- }
348- // structure output according to arcgis polygon geometry spec
349- return {
350- rings : p . coordinates
351- } ;
352- }
353330
354- function ringIsClockwise ( ringToTest : any ) {
355- let total = 0 ;
356- let i = 0 ;
357- const rLength = ringToTest . length ;
358- let pt1 = ringToTest [ i ] ;
359- let pt2 ;
360- for ( i ; i < rLength - 1 ; i ++ ) {
361- pt2 = ringToTest [ i + 1 ] ;
362- total += ( pt2 [ 0 ] - pt1 [ 0 ] ) * ( pt2 [ 1 ] + pt1 [ 1 ] ) ;
363- pt1 = pt2 ;
331+ startPoint = stopPoint ;
364332 }
365- return total >= 0 ;
333+
334+ return { rings } ;
366335}
367336
368- function createLinearRing (
337+ /* istanbul ignore next --@preserve */
338+ function genericPartDecoder (
369339 arr : number [ ] ,
370340 transform : any ,
371341 startPoint : number ,
372- stopPoint : number
342+ stopPoint : number ,
343+ dimensions : number ,
344+ hasZ : boolean ,
345+ hasM : boolean
373346) {
374347 const out = [ ] as any [ ] ;
375- /* istanbul ignore if --@preserve */
376348 if ( arr . length === 0 ) return out ;
377349
378- const initialX = arr [ startPoint ] ;
379- const initialY = arr [ startPoint + 1 ] ;
380- out . push ( transformTuple ( [ initialX , initialY ] , transform ) ) ;
381- let prevX = initialX ;
382- let prevY = initialY ;
383- for ( let i = startPoint + 2 ; i < stopPoint ; i = i + 2 ) {
384- const x = difference ( prevX , arr [ i ] ) ;
385- const y = difference ( prevY , arr [ i + 1 ] ) ;
386- const transformed = transformTuple ( [ x , y ] , transform ) ;
350+ let prevCoords = arr . slice ( startPoint , startPoint + dimensions ) ;
351+ if ( prevCoords . length < 2 ) return out ;
352+
353+ out . push ( transformTuple ( prevCoords , transform , hasZ , hasM ) ) ;
354+
355+ for ( let i = startPoint + dimensions ; i < stopPoint ; i = i + dimensions ) {
356+ const delta = arr . slice ( i , i + dimensions ) ;
357+ if ( delta . length < 2 ) {
358+ continue ;
359+ }
360+
361+ const currentCoords = prevCoords . map ( ( coordinate , index ) =>
362+ // x and y values are relative to the previous coordinate, while z and m values are absolute
363+ // if idx is >= 2, we are handling z or m values so we should return the value only
364+ // if idx is < 2, we are handling x and y values so we should sum the delta to the previous coordinate value
365+ // since x and y values are encoded as deltas in the pbf
366+ index < 2 ? sum ( coordinate , delta [ index ] ) : delta [ index ]
367+ ) ;
368+
369+ const transformed = transformTuple ( currentCoords , transform , hasZ , hasM ) ;
387370 out . push ( transformed ) ;
388- prevX = x ;
389- prevY = y ;
371+ prevCoords = currentCoords ;
390372 }
391373 return out ;
392374}
393375
394376/* istanbul ignore next --@preserve */
395- function transformTuple ( coords : any , transform : any ) {
377+ function closeRing ( ring : any [ ] ) {
378+ const first = ring [ 0 ] ;
379+ const last = ring [ ring . length - 1 ] ;
380+ if ( ! first || ! last ) return ring ;
381+ if ( first [ 0 ] === last [ 0 ] && first [ 1 ] === last [ 1 ] ) return ring ;
382+ return [ ...ring , [ ...first ] ] ;
383+ }
384+
385+ /* istanbul ignore next --@preserve */
386+ function transformTuple (
387+ coords : any ,
388+ transform : any ,
389+ hasZ : boolean ,
390+ hasM : boolean
391+ ) {
392+ const scale = transform ?. scale || { } ;
393+ const translate = transform ?. translate || { } ;
394+
395+ const xScale = scale . xScale ?? 1 ;
396+ const yScale = scale . yScale ?? 1 ;
397+ const zScale = scale . zScale ?? 1 ;
398+ const mScale = scale . mScale ?? 1 ;
399+
400+ const xTranslate = translate . xTranslate ?? 0 ;
401+ const yTranslate = translate . yTranslate ?? 0 ;
402+ const zTranslate = translate . zTranslate ?? 0 ;
403+ const mTranslate = translate . mTranslate ?? 0 ;
404+
396405 let x = coords [ 0 ] ;
397406 let y = coords [ 1 ] ;
398407
399- let z = coords [ 2 ] ? coords [ 2 ] : undefined ;
400- if ( transform . scale ) {
401- x *= transform . scale . xScale ;
402- y *= - transform . scale . yScale ;
403- if ( undefined !== z ) {
404- z *= transform . scale . zScale ;
405- }
408+ let z = hasZ ? coords [ 2 ] : undefined ;
409+ let m = hasM ? coords [ hasZ ? 3 : 2 ] : undefined ;
410+
411+ x = x * xScale + xTranslate ;
412+ y = y * - yScale + yTranslate ;
413+
414+ if ( undefined !== z ) {
415+ z = z * zScale + zTranslate ;
406416 }
407- if ( transform . translate ) {
408- x += transform . translate . xTranslate ;
409- y += transform . translate . yTranslate ;
410- if ( undefined !== z ) {
411- z += transform . translate . zTranslate ;
417+
418+ if ( undefined !== m ) {
419+ if ( m === 0 ) {
420+ m = null ;
421+ } else {
422+ m = m * mScale + mTranslate ;
412423 }
413424 }
425+
414426 const ret = [ x , y ] ;
415427 if ( undefined !== z ) {
416428 ret . push ( z ) ;
417429 }
430+ if ( undefined !== m ) {
431+ ret . push ( m ) ;
432+ }
418433 return ret ;
419434}
420435
421- function difference ( a : any , b : any ) {
436+ function sum ( a : any , b : any ) {
422437 return a + b ;
423438}
0 commit comments