View Issue Details

IDProjectCategoryView StatusLast Update
0038279Lazarus CCRPackagespublic2020-12-30 16:52
Reporterregs Assigned Towp  
PrioritynormalSeverityminorReproducibilityhave not tried
Status resolvedResolutionfixed 
Summary0038279: EPSG3395 projection for LazMapViewer and Yandex Maps
DescriptionAdded EPSG3395 projection support for LazMapViewer and rewrote and refactored all projection calculations to make them more readable. Projection will default to EPSG3857 if not specified in XML file.

Tagslazmapviewer
Widgetset
Attached Files

Activities

regs

2020-12-30 00:20

reporter  

projectiontype.diff (40,245 bytes)   
Index: source/mvengine.pas
===================================================================
--- source/mvengine.pas	(revision 7925)
+++ source/mvengine.pas	(working copy)
@@ -20,18 +20,15 @@
 interface
 
 uses
-  Classes, SysUtils, IntfGraphics, Controls,
+  Classes, SysUtils, IntfGraphics, Controls, Math, TypInfo,
   mvTypes, mvJobQueue, mvMapProvider, mvDownloadEngine, mvCache, mvDragObj;
 
 const
-  EARTH_RADIUS = 6378137;
-  MIN_LATITUDE = -85.05112878;
-  MAX_LATITUDE = 85.05112878;
-  MIN_LONGITUDE = -180;
-  MAX_LONGITUDE = 180;
-  SHIFT = 2 * pi * EARTH_RADIUS / 2.0;
+  EARTH_EQUATORIAL_RADIUS = 6378137;
+  EARTH_POLAR_RADIUS = 6356752.3142;
+  EARTH_CIRCUMFERENCE = 2 * pi * EARTH_EQUATORIAL_RADIUS;
+  EARTH_ECCENTRICITY = sqrt(1-( exp(2*ln(EARTH_POLAR_RADIUS)) / exp(2*ln(EARTH_EQUATORIAL_RADIUS)))); // power doesn't work in interface
 
-
 Type
   TDrawTileEvent = Procedure (const TileId: TTileId; X,Y: integer;
     TileImg: TLazIntfImage) of object;
@@ -89,9 +86,13 @@
       procedure SetUseThreads(AValue: Boolean);
       procedure SetWidth(AValue: integer);
       procedure SetZoom(AValue: integer);
-      function LonLatToMapWin(const aWin: TMapWindow; aPt: TRealPoint): TPoint;
-      Function MapWinToLonLat(const aWin: TMapWindow; aPt : TPoint) : TRealPoint;
+      function DegreesToMapPixels(var aWin: TMapWindow; ALonLat: TRealPoint): TPoint;
+      Function MapPixelsToDegrees(var aWin: TMapWindow; APoints: TPoint): TRealPoint;
+      Function PixelsToDegreesEPSG3395(APoints: TPoint; Zoom: Integer): TRealPoint;
+      Function PixelsToDegreesEPSG3857(APoints: TPoint; Zoom: Integer): TRealPoint;
       Procedure CalculateWin(var aWin: TMapWindow);
+      function DegreesToPixelsEPSG3395(var aWin: TMapWindow; ALonLat: TRealPoint): TPoint;
+      function DegreesToPixelsEPSG3857(var aWin: TMapWindow; ALonLat: TRealPoint): TPoint;
       Procedure Redraw(const aWin: TmapWindow);
       function CalculateVisibleTiles(const aWin: TMapWindow) : TArea;
       function IsCurrentWin(const aWin: TMapWindow) : boolean;
@@ -106,7 +107,7 @@
       constructor Create(aOwner: TComponent); override;
       destructor Destroy; override;
 
-      function AddMapProvider(OpeName: String; Url: String;
+      function AddMapProvider(OpeName: String; ProjectionType: TProjectionType; Url: String;
         MinZoom, MaxZoom, NbSvr: integer; GetSvrStr: TGetSvrStr = nil;
         GetXStr: TGetValStr = nil; GetYStr: TGetValStr = nil;
         GetZStr: TGetValStr = nil): TMapProvider;
@@ -113,8 +114,8 @@
       procedure CancelCurrentDrawing;
       procedure ClearMapProviders;
       procedure GetMapProviders(AList: TStrings);
-      function LonLatToScreen(aPt: TRealPoint): TPoint;
-      function LonLatToWorldScreen(aPt: TRealPoint): TPoint;
+      function LonLatToScreen(ALonLat: TRealPoint): TPoint;
+      function LonLatToWorldScreen(ALonLat: TRealPoint): TPoint;
       function ReadProvidersFromXML(AFileName: String; out AMsg: String): Boolean;
       procedure Redraw;
       Procedure RegisterProviders;
@@ -177,7 +178,7 @@
 implementation
 
 uses
-  Math, Forms, laz2_xmlread, laz2_xmlwrite, laz2_dom,
+  Forms, laz2_xmlread, laz2_xmlwrite, laz2_dom,
   mvJobs, mvGpsObj;
 
 type
@@ -354,9 +355,9 @@
   inherited Destroy;
 end;
 
-function TMapViewerEngine.AddMapProvider(OpeName: String; Url: String;
-  MinZoom, MaxZoom, NbSvr: integer; GetSvrStr: TGetSvrStr; GetXStr: TGetValStr;
-  GetYStr: TGetValStr; GetZStr: TGetValStr): TMapProvider;
+function TMapViewerEngine.AddMapProvider(OpeName: String; ProjectionType: TProjectionType;
+  Url: String; MinZoom, MaxZoom, NbSvr: integer; GetSvrStr: TGetSvrStr;
+  GetXStr: TGetValStr; GetYStr: TGetValStr; GetZStr: TGetValStr): TMapProvider;
 var
   idx :integer;
 Begin
@@ -368,7 +369,7 @@
   end
   else
     Result := TMapProvider(lstProvider.Objects[idx]);
-  Result.AddUrl(Url, NbSvr, MinZoom, MaxZoom, GetSvrStr, GetXStr, GetYStr, GetZStr);
+  Result.AddUrl(Url, ProjectionType, NbSvr, MinZoom, MaxZoom, GetSvrStr, GetXStr, GetYStr, GetZStr);
 end;
 
 function TMapViewerEngine.CalculateVisibleTiles(const aWin: TMapWindow): TArea;
@@ -387,20 +388,18 @@
 
 procedure TMapViewerEngine.CalculateWin(var aWin: TMapWindow);
 var
-  mx, my: Extended;
-  res: Extended;
-  px, py: Int64;
+  PixelLocation: TPoint; // review: coth: Should it use Int64?
 begin
-  mx := aWin.Center.Lon * SHIFT / 180.0;
-  my := ln( tan((90 - aWin.Center.Lat) * pi / 360.0 )) / (pi / 180.0);
-  my := my * SHIFT / 180.0;
 
-  res := (2 * pi * EARTH_RADIUS) / (TILE_SIZE * (1 shl aWin.Zoom));
-  px := Round((mx + shift) / res);
-  py := Round((my + shift) / res);
+  case aWin.MapProvider.ProjectionType of
+    ptEPSG3857: PixelLocation := DegreesToPixelsEPSG3857(aWin, aWin.Center);
+    ptEPSG3395: PixelLocation := DegreesToPixelsEPSG3395(aWin, aWin.Center);
+    else PixelLocation := DegreesToPixelsEPSG3857(aWin, aWin.Center);
+  end;
 
-  aWin.X := aWin.Width div 2 - px;
-  aWin.Y := aWin.Height div 2 - py;
+  aWin.X := Int64(aWin.Width div 2) - PixelLocation.x;
+  aWin.Y := Int64(aWin.Height div 2) - PixelLocation.y;
+
 end;
 
 procedure TMapViewerEngine.CancelCurrentDrawing;
@@ -572,90 +571,234 @@
             (aTile.Y >= 0) and (aTile.Y <= tiles-1);
 end;
 
-function TMapViewerEngine.LonLatToMapWin(const aWin: TMapWindow;
-  aPt: TRealPoint): TPoint;
+function TMapViewerEngine.DegreesToMapPixels(var aWin: TMapWindow; ALonLat: TRealPoint): TPoint;
 var
-  tiles: Int64;
-  circumference: Int64;
-  res: Extended;
-  tmpX,tmpY : Double;
+  PixelLocaltion: TPoint;
 begin
-  tiles := 1 shl aWin.Zoom;
-  circumference := tiles * TILE_SIZE;
-  tmpX := ((aPt.Lon+ 180.0)*circumference)/360.0;
 
-  res := (2 * pi * EARTH_RADIUS) / circumference;
+  case aWin.MapProvider.ProjectionType of
+    ptEPSG3395: PixelLocaltion := DegreesToPixelsEPSG3395(aWin, ALonLat);
+    ptEPSG3857: PixelLocaltion := DegreesToPixelsEPSG3857(aWin, ALonLat);
+    else PixelLocaltion := DegreesToPixelsEPSG3857(aWin, ALonLat);
+  end;
 
-  tmpY := -aPt.Lat;
-  tmpY := ln(tan((degToRad(tmpY) + pi / 2.0) / 2)) *180 / pi;
-  tmpY:= (((tmpY / 180.0) * SHIFT) + SHIFT) / res;
+  Result.X := PixelLocaltion.x + aWin.X;
+  Result.Y := PixelLocaltion.y + aWin.Y;
 
-  tmpX := tmpX + aWin.X;
-  tmpY := tmpY + aWin.Y;
-  Result.X := trunc(tmpX);
-  Result.Y := trunc(tmpY);
 end;
 
-function TMapViewerEngine.LonLatToScreen(aPt: TRealPoint): TPoint;
+// review: coth: Should it use Int64?
+function TMapViewerEngine.DegreesToPixelsEPSG3857(var aWin: TMapWindow; ALonLat: TRealPoint): TPoint;
+const
+  MIN_LATITUDE = -85.05112878;
+  MAX_LATITUDE = 85.05112878;
+  MIN_LONGITUDE = -180;
+  MAX_LONGITUDE = 180;
+var
+  px, py: Extended;
+  pt: TRealPoint;
+begin
+
+  //
+  // https://epsg.io/3857
+  // https://pubs.usgs.gov/pp/1395/report.pdf, page 41
+  // https://en.wikipedia.org/wiki/Web_Mercator_projection
+
+  pt.Lat := Math.EnsureRange(ALonLat.Lat, MIN_LATITUDE, MAX_LATITUDE);
+  pt.Lon := Math.EnsureRange(ALonLat.Lon, MIN_LONGITUDE, MAX_LONGITUDE);
+
+  // note: coth: ** for better readability, but breaking OmniPascal in VSCode
+  // px := ( TILE_SIZE / (2 * pi)) * (2**aWin.Zoom) * (pt.LonRad + pi);
+  // py := ( TILE_SIZE / (2 * pi)) * (2**aWin.Zoom) *
+  px := ( TILE_SIZE / (2 * pi)) * ( Power (2, aWin.Zoom) ) * (pt.LonRad + pi);
+  py := ( TILE_SIZE / (2 * pi)) * ( Power (2, aWin.Zoom) ) *
+          (pi - ln( tan(pi/4 + pt.LatRad/2) ));
+
+  Result.x := Round(px);
+  Result.y := Round(py);
+
+end;
+
+// review: coth: Should it use Int64?
+function TMapViewerEngine.DegreesToPixelsEPSG3395(var aWin: TMapWindow; ALonLat: TRealPoint): TPoint;
+const
+  MIN_LATITUDE = -80;
+  MAX_LATITUDE = 84;
+  MIN_LONGITUDE = -180;
+  MAX_LONGITUDE = 180;
+var
+  px, py, lny, sny: Extended;
+  pt: TRealPoint;
+  cfmpx, cfmpm: Extended;
+  Z: Integer;
+begin
+
+  //
+  // https://epsg.io/3395
+  // https://pubs.usgs.gov/pp/1395/report.pdf, page 44
+
+  pt.Lat := Math.EnsureRange(ALonLat.Lat, MIN_LATITUDE, MAX_LATITUDE);
+  pt.Lon := Math.EnsureRange(ALonLat.Lon, MIN_LONGITUDE, MAX_LONGITUDE);
+
+  Z := 23 - aWin.Zoom;
+  // note: coth: ** for better readability, but breaking OmniPascal in VSCode
+  // cfmpx := 2**31;
+  cfmpx := Power (2, 31);
+  cfmpm := cfmpx / EARTH_CIRCUMFERENCE;
+
+  // note: coth: ** for better readability, but breaking OmniPascal in VSCode
+  // px := (EARTH_CIRCUMFERENCE/2 + EARTH_EQUATORIAL_RADIUS * pt.LonRad) * cfmpm / 2**Z;
+  px := (EARTH_CIRCUMFERENCE/2 + EARTH_EQUATORIAL_RADIUS * pt.LonRad) * cfmpm / Power(2, Z);
+
+  sny := EARTH_ECCENTRICITY * sin(pt.LatRad);
+  lny := tan(pi/4 + pt.LatRad/2) * power((1-sny)/(1+sny), EARTH_ECCENTRICITY/2);
+  // note: coth: ** for better readability, but breaking OmniPascal in VSCode
+  // py := (EARTH_CIRCUMFERENCE/2 - EARTH_EQUATORIAL_RADIUS * ln(lny)) * cfmpm / 2**Z;
+  py := (EARTH_CIRCUMFERENCE/2 - EARTH_EQUATORIAL_RADIUS * ln(lny)) * cfmpm / Power(2, Z);
+
+  Result.x := Round(px);
+  Result.y := Round(py);
+
+end;
+
+function TMapViewerEngine.LonLatToScreen(ALonLat: TRealPoint): TPoint;
 Begin
-  Result := LonLatToMapWin(MapWin, aPt);
+  Result := DegreesToMapPixels(MapWin, ALonLat);
 end;
 
-function TMapViewerEngine.LonLatToWorldScreen(aPt: TRealPoint): TPoint;
+function TMapViewerEngine.LonLatToWorldScreen(ALonLat: TRealPoint): TPoint;
 begin
-  Result := LonLatToScreen(aPt);
+  Result := LonLatToScreen(ALonLat);
   Result.X := Result.X + MapWin.X;
   Result.Y := Result.Y + MapWin.Y;
 end;
 
-function TMapViewerEngine.MapWinToLonLat(const aWin: TMapWindow;
-  aPt: TPoint): TRealPoint;
+function TMapViewerEngine.MapPixelsToDegrees(var aWin: TMapWindow; APoints: TPoint): TRealPoint;
 var
-  tiles: Int64;
-  circumference: Int64;
-  lat: Extended;
-  res: Extended;
+  iMapWidth: Int64;
   mPoint : TPoint;
 begin
-  tiles := 1 shl aWin.Zoom;
-  circumference := tiles * TILE_SIZE;
 
-  mPoint.X := aPt.X - aWin.X;
-  mPoint.Y := aPt.Y - aWin.Y;
+  // review: coth: respective projection check. move to subfunctions? figure out what did i mean here...
+  // note: coth: ** for better readability, but breaking OmniPascal in VSCode
+  // iMapWidth := (2**aWin.Zoom) * TILE_SIZE;
+  iMapWidth := Round(Power (2, aWin.Zoom)) * TILE_SIZE;
 
+  mPoint.X := APoints.X - aWin.X;
+  mPoint.Y := APoints.Y - aWin.Y;
+
   if mPoint.X < 0 then
     mPoint.X := 0
   else
-  if mPoint.X > circumference then
-    mPoint.X := circumference;
+  if mPoint.X > iMapWidth then
+    mPoint.X := iMapWidth;
 
   if mPoint.Y < 0 then
     mPoint.Y := 0
   else
-  if mPoint.Y > circumference then
-    mPoint.Y := circumference;
+  if mPoint.Y > iMapWidth then
+    mPoint.Y := iMapWidth;
 
-  Result.Lon := ((mPoint.X * 360.0) / circumference) - 180.0;
+  case aWin.MapProvider.ProjectionType of
+    ptEPSG3857: Result := PixelsToDegreesEPSG3857(mPoint, aWin.Zoom);
+    ptEPSG3395: Result := PixelsToDegreesEPSG3395(mPoint, aWin.Zoom);
+    else Result := PixelsToDegreesEPSG3857(mPoint, aWin.Zoom);
+  end;
 
-  res := (2 * pi * EARTH_RADIUS) / circumference;
-  lat := ((mPoint.Y * res - SHIFT) / SHIFT) * 180.0;
+end;
 
-  lat := radtodeg (2 * arctan( exp( lat * pi / 180.0)) - pi / 2.0);
-  Result.Lat := -lat;
+Function TMapViewerEngine.PixelsToDegreesEPSG3857(APoints: TPoint; Zoom: Integer): TRealPoint;
+const
+  MIN_LATITUDE = -85.05112878;
+  MAX_LATITUDE = 85.05112878;
+  MIN_LONGITUDE = -180;
+  MAX_LONGITUDE = 180;
+begin
 
-  if Result.Lat > MAX_LATITUDE then
-    Result.Lat := MAX_LATITUDE
-  else
-  if Result.Lat < MIN_LATITUDE then
-    Result.Lat := MIN_LATITUDE;
+  //
+  // https://epsg.io/3857
+  // https://pubs.usgs.gov/pp/1395/report.pdf, page 41
 
-  if Result.Lon > MAX_LONGITUDE then
-    Result.Lon := MAX_LONGITUDE
-  else
-  if Result.Lon < MIN_LONGITUDE then
-    Result.Lon := MIN_LONGITUDE;
+  // note: coth: ** for better readability, but breaking OmniPascal in VSCode
+  // Result.LonRad := ( APoints.X / (( TILE_SIZE / (2*pi)) * 2**Zoom) ) - pi;
+  // Result.LatRad := arctan( sinh(pi - (APoints.Y/TILE_SIZE) / 2**Zoom * pi*2) );
+  Result.LonRad := ( APoints.X / (( TILE_SIZE / (2*pi)) * Power(2, Zoom)) ) - pi;
+  Result.LatRad := arctan( sinh(pi - (APoints.Y/TILE_SIZE) / Power(2, Zoom) * pi*2) );
+
+  Result.Lat := Math.EnsureRange(Result.Lat, MIN_LATITUDE, MAX_LATITUDE);
+  Result.Lon := Math.EnsureRange(Result.Lon, MIN_LONGITUDE, MAX_LONGITUDE);
+
 end;
 
+Function TMapViewerEngine.PixelsToDegreesEPSG3395(APoints: TPoint; Zoom: Integer): TRealPoint;
+
+  function PhiIteration(y, phi: Extended): Extended;
+  var
+    t: Extended;
+  begin
+    t := exp(y/EARTH_EQUATORIAL_RADIUS);
+    phi := pi/2 - 2*arctan( t *
+      Math.power((
+        (1 - EARTH_ECCENTRICITY * sin(phi)) /
+        (1 + EARTH_ECCENTRICITY * sin(phi))),
+        EARTH_ECCENTRICITY/2)
+      );
+    Result := phi;
+  end;
+
+const
+  MIN_LATITUDE = -80;
+  MAX_LATITUDE = 84;
+  MIN_LONGITUDE = -180;
+  MAX_LONGITUDE = 180;
+var
+  LonRad, LatRad: Extended;
+  WorldSize: Int64;
+  Cpm: Extended;
+  Z: Integer;
+  t, phi:  Extended;
+  i: Integer;
+begin
+
+  //
+  // https://epsg.io/3395
+  // https://pubs.usgs.gov/pp/1395/report.pdf, page 44
+
+  Z := 23 - Zoom;
+  // note: coth: ** for better readability, but breaking OmniPascal in VSCode
+  // WorldSize := 2**31;
+  WorldSize := Round(Power(2, 31));
+  Cpm :=  WorldSize / EARTH_CIRCUMFERENCE;
+
+  // note: coth: ** for better readability, but breaking OmniPascal in VSCode
+  // LonRad := (APoints.x / (Cpm/2**Z) - EARTH_CIRCUMFERENCE/2) / EARTH_EQUATORIAL_RADIUS;
+  // LatRad := (APoints.y / (Cpm/2**Z) - EARTH_CIRCUMFERENCE/2);
+  LonRad := (APoints.x / (Cpm/Power(2, Z)) - EARTH_CIRCUMFERENCE/2) / EARTH_EQUATORIAL_RADIUS;
+  LatRad := (APoints.y / (Cpm/Power(2, Z)) - EARTH_CIRCUMFERENCE/2);
+
+  t := pi/2 - 2*arctan(exp(-LatRad/EARTH_EQUATORIAL_RADIUS));
+
+  i := 0;
+  repeat
+    phi := t;
+    t := PhiIteration(LatRad, phi);
+    inc(i);
+    if i>10 then
+      Break;
+      //raise Exception.Create('Phi iteration takes too long.');
+  until
+    abs(phi - t) < 1e-8;
+
+  LatRad := t;
+
+  Result.LonRad := LonRad;
+  Result.LatRad := LatRad;
+
+  Result.Lat := Math.EnsureRange(Result.Lat, MIN_LATITUDE, MAX_LATITUDE);
+  Result.Lon := Math.EnsureRange(Result.Lon, MIN_LONGITUDE, MAX_LONGITUDE);
+
+end;
+
 procedure TMapViewerEngine.MouseDown(Sender: TObject; Button: TMouseButton;
   Shift: TShiftState; X, Y: Integer);
 begin
@@ -705,7 +848,7 @@
   old := TMemObj(Sender.LnkObj);
   aPt.X := old.FWin.Width DIV 2-Sender.OfsX;
   aPt.Y := old.FWin.Height DIV 2-Sender.OfsY;
-  nCenter := MapWinToLonLat(old.FWin,aPt);
+  nCenter := MapPixelsToDegrees(old.FWin,aPt);
   SetCenter(nCenter);
 end;
 
@@ -717,11 +860,12 @@
     lcName: String;
   begin
     lcName := LowerCase(AName);
-    case lcName of
-      'letter': Result := @GetLetterSvr;
-      'yahoo': Result := @GetYahooSvr;
-      else Result := nil;
-    end;
+    if lcName = LowerCase(SVR_LETTER) then
+      Result := @GetSvrLetter
+    else if lcName = LowerCase(SVR_BASE1) then
+      Result := @GetSvrBase1
+    else
+      Result := nil;
   end;
 
   function GetValStr(AName: String): TGetValStr;
@@ -729,12 +873,14 @@
     lcName: String;
   begin
     lcName := Lowercase(AName);
-    case lcName of
-      'quadkey': Result := @GetQuadKey;
-      'yahooy': Result := @GetYahooY;
-      'yahooz': Result := @GetYahooZ;
-      else Result := nil;
-    end;
+    if lcName = LowerCase(STR_QUADKEY) then
+      Result := @GetStrQuadKey
+    else if lcName = LowerCase(STR_YAHOOY) then
+      Result := @GetStrYahooY
+    else if lcName = LowerCase(STR_YAHOOZ) then
+      Result := @GetStrYahooZ
+    else
+      Result := nil;
   end;
 
   function GetAttrValue(ANode: TDOMNode; AttrName: String): String;
@@ -753,6 +899,7 @@
   doc: TXMLDocument = nil;
   node, layerNode: TDOMNode;
   providerName: String;
+  projectionType: TProjectionType;
   url: String;
   minZoom: Integer;
   maxZoom: Integer;
@@ -793,6 +940,8 @@
         s := GetAttrValue(layerNode, 'serverCount');
         if s = '' then svrCount := 1
           else svrCount := StrToInt(s);
+        s := Concat('pt', GetAttrValue(layerNode, 'projection'));
+        projectionType := TProjectionType(GetEnumValue(TypeInfo(TProjectionType), s)); //-1 will default to ptEPSG3857
         svrProc := GetAttrValue(layerNode, 'serverProc');
         xProc := GetAttrValue(layerNode, 'xProc');
         yProc := GetAttrValue(layerNode, 'yProc');
@@ -803,7 +952,7 @@
         ClearMapProviders;
         first := false;
       end;
-      AddMapProvider(providerName,
+      AddMapProvider(providerName, projectionType,
         url, minZoom, maxZoom, svrCount,
         GetSvrStr(svrProc), GetValStr(xProc), GetValStr(yProc), GetValStr(zProc)
       );
@@ -852,54 +1001,58 @@
 var
   HERE1, HERE2: String;
 begin
-//  AddMapProvider('Aucun','',0,30, 0);  ???
 
-  AddMapProvider('Google Normal',
-    'http://mt%serv%.google.com/vt/lyrs=m@145&v=w2.104&x=%x%&y=%y%&z=%z%',
-    0, 19, 4, nil);
-  AddMapProvider('Google Hybrid',
-    'http://mt%serv%.google.com/vt/lyrs=h@145&v=w2.104&x=%x%&y=%y%&z=%z%',
-    0, 19, 4, nil);
-  AddMapProvider('Google Physical',
-    'http://mt%serv%.google.com/vt/lyrs=t@145&v=w2.104&x=%x%&y=%y%&z=%z%',
-    0, 19, 4, nil);
+  // dev links
+  //https://gis-lab.info/forum/viewtopic.php?f=19&t=19763
+  //https://a.tile.openstreetmap.org/16/51693/32520.png
+  //https://vec01.maps.yandex.net/tiles?l=map&x=51693+570&y=32520&z=16&scale=1&lang=ru_RU
+  //https://www.linux.org.ru/forum/development/9038716
+  //https://wiki.openstreetmap.org/wiki/Tiles
+  //https://pubs.usgs.gov/pp/1395/report.pdf
+  //https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Tile_numbers_to_lon..2Flat.
+  //https://mc.bbbike.org/mc/?num=2
+  //https://mc.bbbike.org/mc/?lon=37.62178&lat=55.740937&zoom=14&num=1&mt0=opentopomap&mt1=mapnik-german
+  //https://t.ssl.ak.dynamic.tiles.virtualearth.net/comp/ch/12031010103311?mkt=ru-RU&it=G,BX,RL&shading=hill&n=z&og=677&c4w=1&cstl=vb&src=h
 
   {
-  AddMapProvider('Google Hybrid','http://khm%d.google.com/kh/v=82&x=%x%&y=%y%&z=%z%&s=Ga',4);
-  AddMapProvider('Google Hybrid','http://mt%d.google.com/vt/lyrs=h@145&v=w2.104&x=%d&y=%d&z=%z%',4);
-  AddMapProvider('Google physical','http://mt%d.google.com/vt/lyrs=t@145&v=w2.104&x=%d&y=%d&z=%z%',4);
-  AddMapProvider('Google Physical Hybrid','http://mt%d.google.com/vt/lyrs=t@145&v=w2.104&x=%x%&y=%y%&z=%z%',4);
-  AddMapProvider('Google Physical Hybrid','http://mt%d.google.com/vt/lyrs=h@145&v=w2.104&x=%x%&y=%y%&z=%z%',4);
+  // needs hybrid overlays
+  AddMapProvider('Google Hybrid', ptEPSG3857, 'http://mt%serv%.google.com/vt/lyrs=h@145&v=w2.104&x=%x%&y=%y%&z=%z%', 0, 19, 4, nil);
+  AddMapProvider('Yandex.Maps Hybrid', ptEPSG3395, 'https://vec0%serv%.maps.yandex.net/tiles?l=skl&x=%x%&y=%y%&z=%z%', 0, 19, 4, @GetSvrBase1, nil, nil, nil);
   }
-  //AddMapProvider('OpenStreetMap Osmarender','http://%serv%.tah.openstreetmap.org/Tiles/tile/%z%/%x%/%y%.png',0,20,3, @getLetterSvr); // [Char(Ord('a')+Random(3)), Z, X, Y]));
-  //AddMapProvider('Yahoo Normal','http://maps%serv%.yimg.com/hx/tl?b=1&v=4.3&.intl=en&x=%x%&y=%y%d&z=%d&r=1'        , 0,20,3,@GetYahooSvr, nil, @getYahooY, @GetYahooZ); //(Z+1]));
-  //AddMapProvider('Yahoo Satellite','http://maps%serv%.yimg.com/ae/ximg?v=1.9&t=a&s=256&.intl=en&x=%d&y=%d&z=%d&r=1', 0,20,3,@GetYahooSvr, nil, @getYahooY, @GetYahooZ); //[Random(3)+1, X, YahooY(Y), Z+1]));
-  //AddMapProvider('Yahoo Hybrid','http://maps%serv%.yimg.com/ae/ximg?v=1.9&t=a&s=256&.intl=en&x=%x%&y=%y%&z=%z%&r=1', 0,20,3,@GetYahooSvr, nil, @getYahooY, @GetYahooZ); //[Random(3)+1, X, YahooY(Y), Z+1]));
-  //AddMapProvider('Yahoo Hybrid','http://maps%serv%.yimg.com/hx/tl?b=1&v=4.3&t=h&.intl=en&x=%x%&y=%y%&z=%z%&r=1'    , 0,20,3,@GetYahooSvr, nil, @getYahooY, @GetYahooZ); //[Random(3)+1, X, YahooY(Y), Z+1]));
 
-  // opeName, Url, MinZoom, MaxZoom, NbSvr, GetSvrStr, GetXStr, GetYStr, GetZStr
-  MapWin.MapProvider := AddMapProvider('OpenStreetMap Mapnik',
-    'http://%serv%.tile.openstreetmap.org/%z%/%x%/%y%.png',
-    0, 19, 3, @GetLetterSvr);
-  AddMapProvider('Open Cycle Map',
-    'http://%serv%.tile.opencyclemap.org/cycle/%z%/%x%/%y%.png',
-    0, 18, 3, @getLetterSvr);
-  AddMapProvider('Open Topo Map',
-    'http://%serv%.tile.opentopomap.org/%z%/%x%/%y%.png',
-    0, 19, 3, @getLetterSvr);
-  AddMapProvider('Virtual Earth Bing',
-    'http://ecn.t%serv%.tiles.virtualearth.net/tiles/r%x%?g=671&mkt=en-us&lbl=l1&stl=h&shading=hill',
-    1, 19, 8, nil, @GetQuadKey);
-  AddMapProvider('Virtual Earth Road',
-    'http://r%serv%.ortho.tiles.virtualearth.net/tiles/r%x%.png?g=72&shading=hill',
-    1, 19, 4, nil, @GetQuadKey);
-  AddMapProvider('Virtual Earth Aerial',
-    'http://a%serv%.ortho.tiles.virtualearth.net/tiles/a%x%.jpg?g=72&shading=hill',
-    1, 19, 4, nil, @GetQuadKey);
-  AddMapProvider('Virtual Earth Hybrid',
-    'http://h%serv%.ortho.tiles.virtualearth.net/tiles/h%x%.jpg?g=72&shading=hill',
-    1, 19, 4, nil, @GetQuadKey);
+  // Google
+  AddMapProvider('Google Maps', ptEPSG3857, 'http://mt%serv%.google.com/vt/lyrs=m@145&v=w2.104&x=%x%&y=%y%&z=%z%', 0, 19, 4, nil);
+  AddMapProvider('Google Sattelite', ptEPSG3857, 'http://khm%serv%.google.com/kh/v=863?x=%x%&y=%y%&z=%z%', 0, 19, 4, nil);
+  //AddMapProvider('Google Physical', ptEPSG3857, 'http://mt%serv%.google.com/vt/lyrs=t@145&v=w2.104&x=%x%&y=%y%&z=%z%', 0, 19, 4, nil); // review: doesn't work?
 
+  // Yandex
+  AddMapProvider('Yandex.Maps', ptEPSG3395, 'https://vec0%serv%.maps.yandex.net/tiles?l=map&x=%x%&y=%y%&z=%z%&scale=1&lang=ru_RU', 0, 19, 4, @GetSvrBase1, nil, nil, nil);
+  AddMapProvider('Yandex.Maps Sattelite', ptEPSG3395, 'https://sat0%serv%.maps.yandex.net/tiles?l=sat&x=%x%&y=%y%&z=%z%', 0, 19, 4, @GetSvrBase1, nil, nil, nil);
+  // AddMapProvider('Yandex.Maps Sattelite', ptEPSG3395, 'https://core-sat.maps.yandex.net/tiles?l=sat&x=%x%&y=%y%&z=%z%', 0, 19, 4, @GetSvrBase1, nil, nil, nil);
+
+  MapWin.MapProvider := // Setting OSM as default map
+
+  // debug: coth: comment out
+  //AddMapProvider('OpenStreetMap Mapnik', ptEPSG3857, 'http://%serv%.tile.openstreetmap.org/%z%/%x%/%y%.png', 0, 19, 5, @GetSvrLetter);
+
+  // OSM section
+  AddMapProvider('OpenStreetMap Mapnik', ptEPSG3857, 'http://%serv%.tile.openstreetmap.org/%z%/%x%/%y%.png', 0, 19, 3, @GetSvrLetter);
+  AddMapProvider('OpenStreetMap Wikipedia', ptEPSG3857, 'https://maps.wikimedia.org/osm-intl/%z%/%x%/%y%.png', 0, 19, 3, @GetSvrLetter);
+  AddMapProvider('OpenStreetMap Sputnik', ptEPSG3857, 'https://%serv%.tilessputnik.ru/tiles/kmt2/%z%/%x%/%y%.png', 0, 19, 3, @GetSvrLetter);
+  AddMapProvider('OpenStreetMap.fr Hot', ptEPSG3857, 'https://%serv%.tile.openstreetmap.fr/hot/%z%/%x%/%y%.png', 0, 18, 3, @GetSvrLetter);
+  AddMapProvider('Open Topo Map', ptEPSG3857, 'http://%serv%.tile.opentopomap.org/%z%/%x%/%y%.png', 0, 19, 3, @GetSvrLetter);
+  AddMapProvider('OpenStreetMap.fr Cycle Map', ptEPSG3857, 'https://dev.%serv%.tile.openstreetmap.fr/cyclosm/%z%/%x%/%y%.png', 0, 18, 3, @GetSvrLetter);
+  // todo: requires an optional key
+  AddMapProvider('Open Cycle Map', ptEPSG3857, 'http://%serv%.tile.opencyclemap.org/cycle/%z%/%x%/%y%.png', 0, 18, 3, @GetSvrLetter);
+  AddMapProvider('OpenStreetMap Transport', ptEPSG3857, 'https://%serv%.tile.thunderforest.com/transport/%z%/%x%/%y%.png', 0, 18, 3, @GetSvrLetter);
+
+  // Bing
+  AddMapProvider('Virtual Earth Bing', ptEPSG3857, 'http://ecn.t%serv%.tiles.virtualearth.net/tiles/r%x%?g=671&mkt=en-us&lbl=l1&stl=h&shading=hill', 1, 19, 8, nil, @GetStrQuadKey);
+  // review: remove? fully identical to Bing Maps?
+  //AddMapProvider('Virtual Earth Road', ptEPSG3857, 'http://r%serv%.ortho.tiles.virtualearth.net/tiles/r%x%.png?g=72&shading=hill', 1, 19, 4, nil, @GetStrQuadKey);
+  AddMapProvider('Virtual Earth Aerial', ptEPSG3857, 'http://a%serv%.ortho.tiles.virtualearth.net/tiles/a%x%.jpg?g=72&shading=hill', 1, 19, 4, nil, @GetStrQuadKey);
+  AddMapProvider('Virtual Earth Hybrid', ptEPSG3857, 'http://h%serv%.ortho.tiles.virtualearth.net/tiles/h%x%.jpg?g=72&shading=hill', 1, 19, 4, nil, @GetStrQuadKey);
+
   if (HERE_AppID <> '') and (HERE_AppCode <> '') then begin
     // Registration required to access HERE maps:
     //   https://developer.here.com/?create=Freemium-Basic&keepState=true&step=account
@@ -908,20 +1061,13 @@
     // restart the demo.
     HERE1 := 'http://%serv%.base.maps.api.here.com/maptile/2.1/maptile/newest/';
     HERE2 := '/%z%/%x%/%y%/256/png8?app_id=' + HERE_AppID + '&app_code=' + HERE_AppCode;
-    AddMapProvider('Here Maps', HERE1 + 'normal.day' + HERE2,
-      1, 19, 4, @GetYahooSvr);
-    AddMapProvider('Here Maps Grey', HERE1 + 'normal.day.grey' + HERE2,
-      1, 19, 4, @GetYahooSvr);
-    AddMapProvider('Here Maps Reduced', HERE1 + 'reduced.day' + HERE2,
-      1, 19, 4, @GetYahooSvr);
-    AddMapProvider('Here Maps Transit', HERE1 + 'normal.day.transit' + HERE2,
-      1, 19, 4, @GetYahooSvr);
-    AddMapProvider('Here POI Maps', HERE1 + 'normal.day' + HERE2 + '&pois',
-      1, 19, 4, @GetYahooSvr);
-    AddMapProvider('Here Pedestrian Maps', HERE1 + 'pedestrian.day' + HERE2,
-      1, 19, 4, @GetYahooSvr);
-    AddMapProvider('Here DreamWorks Maps', HERE1 + 'normal.day' + HERE2 + '&style=dreamworks',
-      1, 19, 4, @GetYahooSvr);
+    AddMapProvider('Here WeGo Map', ptEPSG3857, HERE1 + 'normal.day' + HERE2, 1, 19, 4, @GetSvrBase1);
+    AddMapProvider('Here WeGo Grey Map', ptEPSG3857, HERE1 + 'normal.day.grey' + HERE2, 1, 19, 4, @GetSvrBase1);
+    AddMapProvider('Here WeGo Reduced Map', ptEPSG3857, HERE1 + 'reduced.day' + HERE2, 1, 19, 4, @GetSvrBase1);
+    AddMapProvider('Here WeGo Transit Map', ptEPSG3857, HERE1 + 'normal.day.transit' + HERE2, 1, 19, 4, @GetSvrBase1);
+    AddMapProvider('Here WeGo POI Map', ptEPSG3857, HERE1 + 'normal.day' + HERE2 + '&pois', 1, 19, 4, @GetSvrBase1);
+    AddMapProvider('Here WeGo Pedestrian Map', ptEPSG3857, HERE1 + 'pedestrian.day' + HERE2, 1, 19, 4, @GetSvrBase1);
+    AddMapProvider('Here WeGo DreamWorks Map', ptEPSG3857, HERE1 + 'normal.day' + HERE2 + '&style=dreamworks', 1, 19, 4, @GetSvrBase1);
   end;
 
   if (OpenWeatherMap_ApiKey <> '') then begin
@@ -929,50 +1075,18 @@
     //   https://home.openweathermap.org/users/sign_up
     // Store the API key found on the website in the ini file of the demo under
     // key [OpenWeatherMap] and API_Key and restart the demo
-    AddMapProvider('OpenWeatherMap Clouds',
-      'https://tile.openweathermap.org/map/clouds_new/%z%/%x%/%y%.png?appid=' + OpenWeatherMap_ApiKey,
-      1, 19, 1, nil);
-    AddMapProvider('OpenWeatherMap Precipitation',
-      'https://tile.openweathermap.org/map/precipitation_new/%z%/%x%/%y%.png?appid=' + OpenWeatherMap_ApiKey,
-      1, 19, 1, nil);
-    AddMapProvider('OpenWeatherMap Pressure',
-      'https://tile.openweathermap.org/map/pressure_new/%z%/%x%/%y%.png?appid=' + OpenWeatherMap_ApiKey,
-      1, 19, 1, nil);
-    AddMapProvider('OpenWeatherMap Temperature',
-      'https://tile.openweathermap.org/map/temp_new/%z%/%x%/%y%.png?appid=' + OpenWeatherMap_ApiKey,
-      1, 19, 1, nil);
-    AddMapProvider('OpenWeatherMap Wind',
-      'https://tile.openweathermap.org/map/wind_new/%z%/%x%/%y%.png?appid=' + OpenWeatherMap_ApiKey,
-      1, 19, 1, nil);
+    AddMapProvider('OpenWeatherMap Clouds', ptEPSG3857, 'https://tile.openweathermap.org/map/clouds_new/%z%/%x%/%y%.png?appid=' + OpenWeatherMap_ApiKey, 1, 19, 1, nil);
+    AddMapProvider('OpenWeatherMap Precipitation', ptEPSG3857, 'https://tile.openweathermap.org/map/precipitation_new/%z%/%x%/%y%.png?appid=' + OpenWeatherMap_ApiKey, 1, 19, 1, nil);
+    AddMapProvider('OpenWeatherMap Pressure', ptEPSG3857, 'https://tile.openweathermap.org/map/pressure_new/%z%/%x%/%y%.png?appid=' + OpenWeatherMap_ApiKey, 1, 19, 1, nil);
+    AddMapProvider('OpenWeatherMap Temperature', ptEPSG3857, 'https://tile.openweathermap.org/map/temp_new/%z%/%x%/%y%.png?appid=' + OpenWeatherMap_ApiKey, 1, 19, 1, nil);
+    AddMapProvider('OpenWeatherMap Wind', ptEPSG3857, 'https://tile.openweathermap.org/map/wind_new/%z%/%x%/%y%.png?appid=' + OpenWeatherMap_ApiKey, 1, 19, 1, nil);
   end;
 
-  { The Ovi Maps (former Nokia maps) are no longer available.
-
-  AddMapProvider('Ovi Normal',
-    'http://%serv%.maptile.maps.svc.ovi.com/maptiler/v2/maptile/newest/normal.day/%z%/%x%/%y%/256/png8',
-    0, 20, 5, @GetLetterSvr);
-  AddMapProvider('Ovi Satellite',
-    'http://%serv%.maptile.maps.svc.ovi.com/maptiler/v2/maptile/newest/satellite.day/%z%/%x%/%y%/256/png8',
-    0, 20, 5, @GetLetterSvr);
-  AddMapProvider('Ovi Hybrid',
-    'http://%serv%.maptile.maps.svc.ovi.com/maptiler/v2/maptile/newest/hybrid.day/%z%/%x%/%y%/256/png8',
-    0, 20, 5, @GetLetterSvr);
-  AddMapProvider('Ovi Physical',
-    'http://%serv%.maptile.maps.svc.ovi.com/maptiler/v2/maptile/newest/terrain.day/%z%/%x%/%y%/256/png8',
-    0, 20, 5, @GetLetterSvr);
-  }
-
-  {
-  AddMapProvider('Yahoo Normal','http://maps%serv%.yimg.com/hx/tl?b=1&v=4.3&.intl=en&x=%x%&y=%y%d&z=%d&r=1'        , 0,20,3,@GetYahooSvr, nil, @getYahooY, @GetYahooZ); //(Z+1]));
-  AddMapProvider('Yahoo Satellite','http://maps%serv%.yimg.com/ae/ximg?v=1.9&t=a&s=256&.intl=en&x=%d&y=%d&z=%d&r=1', 0,20,3,@GetYahooSvr, nil, @getYahooY, @GetYahooZ); //[Random(3)+1, X, YahooY(Y), Z+1]));
-  AddMapProvider('Yahoo Hybrid','http://maps%serv%.yimg.com/ae/ximg?v=1.9&t=a&s=256&.intl=en&x=%x%&y=%y%&z=%z%&r=1', 0,20,3,@GetYahooSvr, nil, @getYahooY, @GetYahooZ); //[Random(3)+1, X, YahooY(Y), Z+1]));
-  AddMapProvider('Yahoo Hybrid','http://maps%serv%.yimg.com/hx/tl?b=1&v=4.3&t=h&.intl=en&x=%x%&y=%y%&z=%z%&r=1'    , 0,20,3,@GetYahooSvr, nil, @getYahooY, @GetYahooZ); //[Random(3)+1, X, YahooY(Y), Z+1]));
-  }
 end;
 
 function TMapViewerEngine.ScreenToLonLat(aPt: TPoint): TRealPoint;
 begin
-  Result := MapWinToLonLat(MapWin, aPt);
+  Result := MapPixelsToDegrees(MapWin, aPt);
 end;
 
 procedure TMapViewerEngine.SetActive(AValue: boolean);
@@ -1040,6 +1154,7 @@
   begin
     MapWin.MapProvider := TMapProvider(lstProvider.Objects[idx]);
     ConstraintZoom(MapWin);
+    CalculateWin(MapWin);
   end
   else
     MapWin.MapProvider := nil;
@@ -1099,7 +1214,7 @@
     begin
        Cache.GetFromCache(EnvTile.Win.MapProvider, EnvTile.Tile, img);
        X := EnvTile.Win.X + EnvTile.Tile.X * TILE_SIZE; // begin of X
-       Y := EnvTile.Win.Y + EnvTile.Tile.Y * TILE_SIZE; // begin of X
+       Y := EnvTile.Win.Y + EnvTile.Tile.Y * TILE_SIZE; // begin of Y
        DrawTile(EnvTile.Tile, X, Y, img);
     end;
   finally
@@ -1151,8 +1266,8 @@
   BottomRight.Y := tmpWin.Height;
   Repeat
     CalculateWin(tmpWin);
-    visArea.TopLeft := MapWinToLonLat(tmpWin, TopLeft);
-    visArea.BottomRight := MapWinToLonLat(tmpWin, BottomRight);
+    visArea.TopLeft := MapPixelsToDegrees(tmpWin, TopLeft);
+    visArea.BottomRight := MapPixelsToDegrees(tmpWin, BottomRight);
     if AreaInsideArea(aArea, visArea) then
       break;
     dec(tmpWin.Zoom);
@@ -1221,8 +1336,8 @@
 
   Each part can exhibit a unit identifier, such as °, ', or ". BUT: they are
   ignored. This means that an input string 50°30" results in the output value 50.5
-  although the second part is marked as seconds, not minutes! 
-  
+  although the second part is marked as seconds, not minutes!
+
   Hemisphere suffixes ('N', 'S', 'E', 'W') are supported at the end of the input string.
 }
 function TryStrToGps(const AValue: String; out ADeg: Double): Boolean;
@@ -1365,7 +1480,7 @@
     d_radians := PI * 2.0
   else
     d_radians := arccos(arg);
-  Result := EARTH_RADIUS * d_radians;
+  Result := EARTH_EQUATORIAL_RADIUS * d_radians;
 
   case AUnits of
     duMeters: ;
Index: source/mvmapprovider.pas
===================================================================
--- source/mvmapprovider.pas	(revision 7925)
+++ source/mvmapprovider.pas	(working copy)
@@ -15,7 +15,7 @@
 interface
 
 uses
-  Classes, SysUtils, laz2_xmlwrite, laz2_dom;
+  Classes, SysUtils, laz2_xmlwrite, laz2_dom, TypInfo, Math;
 
 type
 
@@ -28,6 +28,7 @@
 
   TGetSvrStr = function (id: integer): string;
   TGetValStr = function (const Tile: TTileId): String;
+  TProjectionType = (ptEPSG3857, ptEPSG3395);
 
   TMapProvider = class;
 
@@ -49,6 +50,7 @@
       idServer: Array of Integer;
       FName: String;
       FUrl: Array of string;
+      FProjectionType: Array of TProjectionType;
       FNbSvr: Array of integer;
       FGetSvrStr: Array of TGetSvrStr;
       FGetXStr: Array of TGetValStr;
@@ -60,12 +62,13 @@
       FTileHandling: TRTLCriticalSection;
       function GetLayerCount: integer;
       procedure SetLayer(AValue: integer);
+      function GetProjectionType: TProjectionType;
     public
       constructor Create(AName: String);
       destructor Destroy; override;
       function AppendTile(aTile: TBaseTile): integer;
       procedure RemoveTile(aTile: TBaseTile);
-      procedure AddURL(Url: String; NbSvr, aMinZoom, aMaxZoom: integer;
+      procedure AddURL(Url: String; ProjectionType: TProjectionType; NbSvr, aMinZoom, aMaxZoom: integer;
         GetSvrStr: TGetSvrStr; GetXStr: TGetValStr; GetYStr: TGetValStr;
         GetZStr: TGetValStr);
       procedure GetZoomInfos(out AZoomMin, AZoomMax: integer);
@@ -72,26 +75,32 @@
       function GetUrlForTile(id: TTileId): String;
       procedure ToXML(ADoc: TXMLDocument; AParentNode: TDOMNode);
       property Name: String read FName;
+      property ProjectionType: TProjectionType read GetProjectionType;
       property LayerCount: integer read GetLayerCount;
       property Layer: integer read FLayer write SetLayer;
   end;
 
+function GetSvrLetter(id: integer): String;
+function GetSvrBase1(id: integer): String;
+function GetStrYahooY(const Tile: TTileId): string; // Idea: Deprecate, as Yahoo Maps are dead
+function GetStrYahooZ(const Tile: TTileId): string; // Idea: Deprecate, as Yahoo Maps are dead
+function GetStrQuadKey(const Tile: TTileId): string;
 
-function GetLetterSvr(id: integer): String;
-function GetYahooSvr(id: integer): String;
-function GetYahooY(const Tile: TTileId): string;
-function GetYahooZ(const Tile: TTileId): string;
-function GetQuadKey(const Tile: TTileId): string;
+const
+  SVR_LETTER = 'Letter';
+  SVR_BASE1  = 'Base1';
+  STR_YAHOOY = 'YahooY'; // Idea: Deprecate, as Yahoo Maps are dead
+  STR_YAHOOZ = 'YahooZ'; // Idea: Deprecate, as Yahoo Maps are dead
+  STR_QUADKEY = 'QuadKey';
 
-
 implementation
 
-function GetLetterSvr(id: integer): String;
+function GetSvrLetter(id: integer): String;
 begin
   Result := Char(Ord('a') + id);
 end;
 
-function GetQuadKey(const Tile: TTileId): string;
+function GetStrQuadKey(const Tile: TTileId): string;
 var
   i, d, m: Longword;
 begin
@@ -110,17 +119,17 @@
   end;
 end;
 
-function GetYahooSvr(id: integer): String;
+function GetSvrBase1(id: integer): String;
 Begin
   Result := IntToStr(id + 1);
 end;
 
-function GetYahooY(const Tile : TTileId): string;
+function GetStrYahooY(const Tile : TTileId): string;
 begin
   Result := IntToStr( -(Tile.Y - (1 shl Tile.Z) div 2) - 1);
 end;
 
-function GetYahooZ(const Tile : TTileId): string;
+function GetStrYahooZ(const Tile : TTileId): string;
 Begin
   result := IntToStr(Tile.Z + 1);
 end;
@@ -160,6 +169,11 @@
   FLayer:=AValue;
 end;
 
+function TMapProvider.GetProjectionType: TProjectionType;
+begin
+  Result := FProjectionType[layer];
+end;
+
 constructor TMapProvider.Create(AName: String);
 begin
   FName := aName;
@@ -172,6 +186,7 @@
 begin
   Finalize(idServer);
   Finalize(FName);
+  Finalize(FProjectionType);
   Finalize(FUrl);
   Finalize(FNbSvr);
   Finalize(FGetSvrStr);
@@ -221,7 +236,7 @@
     end;
 end;
 
-procedure TMapProvider.AddURL(Url: String; NbSvr, aMinZoom, aMaxZoom: integer;
+procedure TMapProvider.AddURL(Url: String; ProjectionType: TProjectionType; NbSvr, aMinZoom, aMaxZoom: integer;
   GetSvrStr: TGetSvrStr; GetXStr: TGetValStr; GetYStr: TGetValStr;
   GetZStr: TGetValStr);
 var
@@ -230,6 +245,7 @@
   nb := Length(FUrl)+1;
   SetLength(IdServer, nb);
   SetLength(FUrl, nb);
+  SetLength(FProjectionType, nb);
   SetLength(FNbSvr, nb);
   SetLength(FGetSvrStr, nb);
   SetLength(FGetXStr, nb);
@@ -239,6 +255,7 @@
   SetLength(FMaxZoom, nb);
   nb := High(FUrl);
   FUrl[nb] := Url;
+  FProjectionType[nb] := ProjectionType;
   FNbSvr[nb] := NbSvr;
   FMinZoom[nb] := aMinZoom;
   FMaxZoom[nb] := aMaxZoom;
@@ -297,7 +314,9 @@
   node := ADoc.CreateElement('map_provider');
   node.SetAttribute('name', FName);
   AParentNode.AppendChild(node);
+  
   for i:=0 to LayerCount-1 do begin
+  
     layerNode := ADoc.CreateElement('layer');
     node.AppendChild(layernode);
     layerNode.SetAttribute('url', FUrl[i]);
@@ -304,29 +323,34 @@
     layerNode.SetAttribute('minZoom', IntToStr(FMinZoom[i]));
     layerNode.SetAttribute('maxZoom', IntToStr(FMaxZoom[i]));
     layerNode.SetAttribute('serverCount', IntToStr(FNbSvr[i]));
+    s := GetEnumName(TypeInfo(TProjectionType), Ord(FProjectionType[i]));
+    if ( s.StartsWith('pt') ) then
+      s := s.Substring(2);
+    layerNode.SetAttribute('projection', s);
 
-    if FGetSvrStr[i] = @getLetterSvr then s := 'Letter'
-      else if FGetSvrStr[i] = @GetYahooSvr then s := 'Yahoo'
-      else if FGetSvrstr[i] <> nil then s := 'unknown'
-      else s := '';
+    if FGetSvrStr[i] = @GetSvrLetter then s := SVR_LETTER
+    else if FGetSvrStr[i] = @GetSvrBase1 then s := SVR_BASE1
+    // else if FGetSvrstr[i] <> nil then s := 'unknown'
+    else s := '';
     if s <> '' then layerNode.SetAttribute('serverProc', s);
 
-    if FGetXStr[i] = @GetQuadKey then s := 'QuadKey'
-      else if FGetXStr[i] <> nil then s := '(unknown)'
-      else s := '';
+    if FGetXStr[i] = @GetStrQuadKey then s := STR_QUADKEY
+    // else if FGetXStr[i] <> nil then s := '(unknown)'
+    else s := '';
     if s <> '' then layerNode.SetAttribute('xProc', s);
 
-    if FGetYStr[i] = @GetQuadKey then s := 'QuadKey'
-      else if FGetYStr[i] = @GetYahooY then s := 'YahooY'
-      else if FGetYStr[i] <> nil then s := '(unknown)'
-      else s := '';
+    if FGetYStr[i] = @GetStrQuadKey then s := STR_QUADKEY
+    else if FGetYStr[i] = @GetStrYahooY then s := STR_YAHOOY
+    // else if FGetYStr[i] <> nil then s := '(unknown)'
+    else s := '';
     if s <> '' then layerNode.SetAttribute('yProc', s);
 
-    if FGetZStr[i] = @GetQuadKey then s := 'QuadKey'
-      else if FGetZStr[i] = @GetYahooZ then s := 'YahooZ'
-      else if FGetZStr[i] <> nil then s := '(unknown)'
-      else s := '';
+    if FGetZStr[i] = @GetStrQuadKey then s := STR_QUADKEY
+    else if FGetZStr[i] = @GetStrYahooZ then s := STR_YAHOOZ
+    // else if FGetZStr[i] <> nil then s := '(unknown)'
+    else s := '';
     if s <> '' then layerNode.SetAttribute('zProc', s);
+
   end;
 end;
 
Index: source/mvtypes.pas
===================================================================
--- source/mvtypes.pas	(revision 7925)
+++ source/mvtypes.pas	(working copy)
@@ -10,11 +10,12 @@
 unit mvTypes;
 
 {$mode objfpc}{$H+}
+{$MODESWITCH ADVANCEDRECORDS}
 
 interface
 
 uses
-  Classes, SysUtils;
+  Classes, SysUtils, Math;
 
 const
   TILE_SIZE = 256;
@@ -21,7 +22,7 @@
   PALETTE_PAGE = 'Misc';
 
 Type
-    { TArea }
+  { TArea }
   TArea = record
     top, left, bottom, right: Int64;
   end;
@@ -28,8 +29,17 @@
 
   { TRealPoint }
   TRealPoint = Record
-    Lon : Double;
-    Lat : Double;
+    public
+      Lon: Double;
+      Lat: Double;
+    private
+      function GetLonRad: Extended;
+      procedure SetLonRad(AValue: Extended);
+      function GetLatRad: Extended;
+      procedure SetLatRad(AValue: Extended);
+    public
+      property LonRad: Extended read GetLonRad write SetLonRad;
+      property LatRad: Extended read GetLatRad write SetLatRad;
   end;
 
   { TRealArea }
@@ -40,5 +50,23 @@
 
 implementation
 
+function TRealPoint.GetLonRad: Extended;
+begin
+  Result := degtorad(Self.Lon);
+end;
+procedure TRealPoint.SetLonRad(AValue: Extended);
+begin
+  Self.Lon := radtodeg(AValue);
+end;
+
+function TRealPoint.GetLatRad: Extended;
+begin
+  Result := degtorad(Self.Lat);
+end;
+procedure TRealPoint.SetLatRad(AValue: Extended);
+begin
+  Self.Lat := radtodeg(AValue);
+end;
+
 end.
 
projectiontype.diff (40,245 bytes)   

wp

2020-12-30 16:52

developer   ~0127942

Applied with minor modifications. Fixed also the handling of the API key for OpenCycleMa and OpenStreetMap_Transport.
Please test and close the report if ok.

Issue History

Date Modified Username Field Change
2020-12-30 00:20 regs New Issue
2020-12-30 00:20 regs File Added: projectiontype.diff
2020-12-30 00:21 regs Tag Attached: lazmapviewer
2020-12-30 00:57 wp Assigned To => wp
2020-12-30 00:57 wp Status new => assigned
2020-12-30 16:52 wp Status assigned => resolved
2020-12-30 16:52 wp Resolution open => fixed
2020-12-30 16:52 wp Note Added: 0127942