@@ -494,51 +494,76 @@ namespace AzureMaps
494
494
/// <param name =" bounds" >A bounding box defined as an array of numbers in the format of [west, south, east, north].</param >
495
495
/// <param name =" mapWidth" >Map width in pixels.</param >
496
496
/// <param name =" mapHeight" >Map height in pixels.</param >
497
- /// <param name =" padding" >Width in pixels to use to create a buffer around the map. This is to keep markers from being cut off on the edge</param >
498
- /// <param name =" tileSize" >The size of the tiles in the tile pyramid.</param >
499
497
/// <param name =" latitude" >Output parameter receiving the center latitude coordinate.</param >
500
498
/// <param name =" longitude" >Output parameter receiving the center longitude coordinate.</param >
501
499
/// <param name =" zoom" >Output parameter receiving the zoom level</param >
502
- public static void BestMapView (double [] bounds , double mapWidth , double mapHeight , int padding , int tileSize , out double centerLat , out double centerLon , out double zoom )
500
+ /// <param name =" padding" >Width in pixels to use to create a buffer around the map. This is to keep markers from being cut off on the edge. Default: 0</param >
501
+ /// <param name =" tileSize" >The size of the tiles in the tile pyramid. Default: 512</param >
502
+ /// <param name =" maxZoom" >Optional maximum zoom level to return. Useful when the bounding box represents a very small area. Default: 24</param >
503
+ /// <param name =" allowFloatZoom" >Specifies if the returned zoom level should be a float or rounded down to an whole integer zoom level. Default: true</param >
504
+ public static void BestMapView (BoundingBox bounds , double mapWidth , double mapHeight , out double centerLat , out double centerLon , out double zoom , int padding = 0 , int tileSize = 512 , double maxZoom = 24 , bool allowFloatZoom = true )
503
505
{
504
- if (bounds == null || bounds .Length < 4 )
505
- {
506
- centerLat = 0 ;
507
- centerLon = 0 ;
508
- zoom = 1 ;
509
- return ;
510
- }
511
-
512
- double boundsDeltaX ;
513
-
514
- // Check if east value is greater than west value which would indicate that bounding box crosses the antimeridian.
515
- if (bounds [2 ] > bounds [0 ])
516
- {
517
- boundsDeltaX = bounds [2 ] - bounds [0 ];
518
- centerLon = (bounds [2 ] + bounds [0 ]) / 2 ;
519
- }
520
- else
521
- {
522
- boundsDeltaX = 360 - (bounds [0 ] - bounds [2 ]);
523
- centerLon = ((bounds [2 ] + bounds [0 ]) / 2 + 360 ) % 360 - 180 ;
524
- }
525
-
526
- var ry1 = Math .Log ((Math .Sin (bounds [1 ] * Math .PI / 180 ) + 1 ) / Math .Cos (bounds [1 ] * Math .PI / 180 ));
527
- var ry2 = Math .Log ((Math .Sin (bounds [3 ] * Math .PI / 180 ) + 1 ) / Math .Cos (bounds [3 ] * Math .PI / 180 ));
528
- var ryc = (ry1 + ry2 ) / 2 ;
529
-
530
- centerLat = Math .Atan (Math .Sinh (ryc )) * 180 / Math .PI ;
531
-
532
- var resolutionHorizontal = boundsDeltaX / (mapWidth - padding * 2 );
533
-
534
- var vy0 = Math .Log (Math .Tan (Math .PI * (0 . 25 + centerLat / 360 )));
535
- var vy1 = Math .Log (Math .Tan (Math .PI * (0 . 25 + bounds [3 ] / 360 )));
536
- var zoomFactorPowered = (mapHeight * 0 . 5 - padding ) / (40 . 7436654315252 * (vy1 - vy0 ));
537
- var resolutionVertical = 360 . 0 / (zoomFactorPowered * tileSize );
538
-
539
- var resolution = Math .Max (resolutionHorizontal , resolutionVertical );
540
-
541
- zoom = Math .Log (360 / (resolution * tileSize ), 2 );
506
+ centerLat = 0 ;
507
+ centerLon = 0 ;
508
+ zoom = 0 ;
509
+
510
+ if (bounds != null && mapWidth > 0 && mapHeight > 0 )
511
+ {
512
+ // Ensure padding is valid.
513
+ padding = Math .Abs (padding );
514
+
515
+ // Ensure max zoom is within valid range.
516
+ maxZoom = Clip (maxZoom , 0 , 24 );
517
+
518
+ // Do pixel calculations at zoom level 24 as that will provide a high level of visual accuracy.
519
+ int pixelZoom = 24 ;
520
+
521
+ // Calculate mercator pixel coordinate at zoom level 24.
522
+ var wnPixel = PositionToGlobalPixel (new double [] { bounds [0 ], bounds [3 ] }, pixelZoom , tileSize );
523
+ var esPixel = PositionToGlobalPixel (new double [] { bounds [2 ], bounds [1 ] }, pixelZoom , tileSize );
524
+
525
+ // Calculate the pixel distance between pixels for each axis.
526
+ double dx = esPixel [0 ] - wnPixel [0 ];
527
+ double dy = esPixel [1 ] - wnPixel [1 ];
528
+
529
+ // Calculate the average pixel positions to get the visual center.
530
+ double xAvg = (esPixel [0 ] + wnPixel [0 ]) / 2 ;
531
+ double yAvg = (esPixel [1 ] + wnPixel [1 ]) / 2 ;
532
+
533
+ // Determine if the bounding box crosses the antimeridian. (West pixel will be greater than East pixel).
534
+ if (wnPixel [0 ] > esPixel [0 ])
535
+ {
536
+ double mapSize = MapSize (24 , tileSize );
537
+
538
+ // We are interested in the opposite area of the map. Calculate the opposite area and visual center.
539
+ dx = mapSize - Math .Abs (dx );
540
+
541
+ // Offset the visual center by half the global map width at zoom 24 on the x axis.
542
+ xAvg += mapSize / 2 ;
543
+ }
544
+
545
+ // Convert visual center pixel from zoom 24 to lngLat.
546
+ center = GlobalPixelToPosition (new Pixel (xAvg , yAvg ), pixelZoom , tileSize );
547
+
548
+ // Calculate scale of screen pixels per unit on the Web Mercator plane.
549
+ double scaleX = (mapWidth - padding * 2 ) / Math .Abs (dx ) * Math .Pow (2 , pixelZoom );
550
+ double scaleY = (mapHeight - padding * 2 ) / Math .Abs (dy ) * Math .Pow (2 , pixelZoom );
551
+
552
+ // Calculate zoom levels based on the x/y scales. Choose the most zoomed out value.
553
+ zoom = Math .Max (0 , Math .Min (maxZoom , Math .Log2 (Math .Abs (Math .Min (scaleX , scaleY )))));
554
+
555
+ // Round down zoom level if float values are not desired.
556
+ if (! allowFloatZoom )
557
+ {
558
+ zoom = Math .Floor (zoom );
559
+ }
560
+ }
561
+
562
+ return new CameraOptions
563
+ {
564
+ Center = center ,
565
+ Zoom = zoom
566
+ };
542
567
}
543
568
}
544
569
}
@@ -873,51 +898,70 @@ module AzureMaps {
873
898
* @param mapHeight Map height in pixels.
874
899
* @param padding Width in pixels to use to create a buffer around the map. This is to keep markers from being cut off on the edge.
875
900
* @param tileSize The size of the tiles in the tile pyramid.
901
+ * @param maxZoom Optional maximum zoom level to return. Useful when the bounding box represents a very small area.
902
+ * @param allowFloatZoom Specifies if the returned zoom level should be a float or rounded down to an whole integer zoom level.
876
903
* @returns The center and zoom level to best position the map view over the provided bounding box.
877
904
*/
878
- public static BestMapView(bounds : number [], mapWidth : number , mapHeight : number , padding : number , tileSize : number ): { center: number [], zoom: number } {
879
- if (bounds == null || bounds .length < 4 ) {
880
- return {
881
- center: [0 , 0 ],
882
- zoom: 1
883
- };
884
- }
885
-
886
- var boundsDeltaX: number ;
887
- var centerLat: number ;
888
- var centerLon: number ;
889
-
890
- // Check if east value is greater than west value which would indicate that bounding box crosses the antimeridian.
891
- if (bounds [2 ] > bounds [0 ]) {
892
- boundsDeltaX = bounds [2 ] - bounds [0 ];
893
- centerLon = (bounds [2 ] + bounds [0 ]) / 2 ;
894
- }
895
- else {
896
- boundsDeltaX = 360 - (bounds [0 ] - bounds [2 ]);
897
- centerLon = ((bounds [2 ] + bounds [0 ]) / 2 + 360 ) % 360 - 180 ;
898
- }
899
-
900
- var ry1 = Math .log ((Math .sin (bounds [1 ] * Math .PI / 180 ) + 1 ) / Math .cos (bounds [1 ] * Math .PI / 180 ));
901
- var ry2 = Math .log ((Math .sin (bounds [3 ] * Math .PI / 180 ) + 1 ) / Math .cos (bounds [3 ] * Math .PI / 180 ));
902
- var ryc = (ry1 + ry2 ) / 2 ;
903
-
904
- centerLat = Math .atan (Math .sinh (ryc )) * 180 / Math .PI ;
905
-
906
- var resolutionHorizontal = boundsDeltaX / (mapWidth - padding * 2 );
907
-
908
- var vy0 = Math .log (Math .tan (Math .PI * (0.25 + centerLat / 360 )));
909
- var vy1 = Math .log (Math .tan (Math .PI * (0.25 + bounds [3 ] / 360 )));
910
- var zoomFactorPowered = (mapHeight * 0.5 - padding ) / (40.7436654315252 * (vy1 - vy0 ));
911
- var resolutionVertical = 360.0 / (zoomFactorPowered * tileSize );
912
-
913
- var resolution = Math .max (resolutionHorizontal , resolutionVertical );
914
-
915
- var zoom = Math .log2 (360 / (resolution * tileSize ));
916
-
917
- return {
918
- center: [centerLon , centerLat ],
919
- zoom: zoom
920
- };
905
+ public static BestMapView(bounds : number , mapWidth : number , mapHeight : number , padding : number = 0 , tileSize : number = 512 , maxZoom : number = 24 , allowFloatZoom : boolean = true ): { center: number [], zoom: number } {
906
+ // Ensure valid bounds and map dimensions are provided.
907
+ if (bounds == null || bounds .length < 4 || ! mapWidth || ! mapHeight || mapWidth <= 0 || mapHeight <= 0 ) {
908
+ return {
909
+ center: [0 , 0 ],
910
+ zoom: 0
911
+ };
912
+ }
913
+
914
+ // Ensure padding is valid.
915
+ padding = Math .abs (padding || 0 );
916
+
917
+ // Ensure max zoom is within valid range.
918
+ maxZoom = this .Clip (maxZoom , 0 , 24 );
919
+
920
+ // Do pixel calculations at zoom level 24 as that will provide a high level of visual accuracy.
921
+ const pixelZoom = 24 ;
922
+
923
+ // Calculate mercator pixel coordinate at zoom level 24.
924
+ const wnPixel = this .PositionToGlobalPixel ([bounds [0 ], bounds [3 ]], pixelZoom , tileSize );
925
+ const esPixel = this .PositionToGlobalPixel ([bounds [2 ], bounds [1 ]], pixelZoom , tileSize );
926
+
927
+ // Calculate the pixel distance between pixels for each axis.
928
+ let dx = esPixel [0 ] - wnPixel [0 ];
929
+ const dy = esPixel [1 ] - wnPixel [1 ];
930
+
931
+ // Calculate the average pixel positions to get the visual center.
932
+ let xAvg = (esPixel [0 ] + wnPixel [0 ]) / 2 ;
933
+ const yAvg = (esPixel [1 ] + wnPixel [1 ]) / 2 ;
934
+
935
+ // Determine if the bounding box crosses the antimeridian. (West pixel will be greater than East pixel).
936
+ if (wnPixel [0 ] > esPixel [0 ]) {
937
+ const mapSize = this .MapSize (24 , tileSize );
938
+
939
+ // We are interested in the opposite area of the map. Calculate the opposite area and visual center.
940
+ dx = mapSize - Math .abs (dx );
941
+
942
+ // Offset the visual center by half the global map width at zoom 24 on the x axis.
943
+ xAvg += mapSize / 2 ;
944
+ }
945
+
946
+ // Convert visual center pixel from zoom 24 to lngLat.
947
+ const centerLngLat = this .GlobalPixelToPosition ([xAvg , yAvg ], pixelZoom , tileSize );
948
+
949
+ // Calculate scale of screen pixels per unit on the Web Mercator plane.
950
+ const scaleX = (mapWidth - padding * 2 ) / Math .abs (dx ) * Math .pow (2 , pixelZoom );
951
+ const scaleY = (mapHeight - padding * 2 ) / Math .abs (dy ) * Math .pow (2 , pixelZoom );
952
+
953
+ // Calculate zoom levels based on the x/y scales. Choose the most zoomed out value.
954
+ let zoom = Math .max (0 , Math .min (maxZoom , Math .log2 (Math .abs (Math .min (scaleX , scaleY )))));
955
+
956
+ // Round down zoom level if float values are not desired.
957
+ if (! allowFloatZoom ) {
958
+ zoom = Math .floor (zoom );
959
+ }
960
+
961
+ return {
962
+ center: centerLngLat ,
963
+ zoom
964
+ };
921
965
}
922
966
}
923
967
}
0 commit comments