Difference between revisions of "LazMapViewer"

From Lazarus wiki
(About: Getting started)
(List of GPS Objects: Example for adding a GPS point)
 
(18 intermediate revisions by the same user not shown)
Line 4: Line 4:
  
 
===Authors===
 
===Authors===
The initial version of the package was written by Maciej Kaczkowski and later improved by members of the Lazarus forum.
+
The initial version of the package was written by Maciej Kaczkowski and later improved by members of the Lazarus forum. The package is currently maintained by Werner Pamler (wp_xxyyzzz-at-gmx-dot-net).
  
 
===License===
 
===License===
Line 39: Line 39:
 
|}
 
|}
  
==Getting Started==
+
==The Main Map Viewer: TMapView==
 +
 
 +
===Getting Started===
 
Here is a short tutorial to create your first map.
 
Here is a short tutorial to create your first map.
  
===Preparation===
+
====Preparation====
 
* Drop a <tt>TMapView</tt> component on the form and size it as needed. The component is on the ''Misc'' tab of the Lazarus component palette.
 
* Drop a <tt>TMapView</tt> component on the form and size it as needed. The component is on the ''Misc'' tab of the Lazarus component palette.
 
* Add this handler for the form's <tt>OnShow</tt> or <tt>OnActivate</tt> event and set the <tt>Active</tt> property of the <tt>MapView</tt> component to <tt>true</tt>; in principle, this should be possible also in the object inspectore, however, does not work at the time of this writing.
 
* Add this handler for the form's <tt>OnShow</tt> or <tt>OnActivate</tt> event and set the <tt>Active</tt> property of the <tt>MapView</tt> component to <tt>true</tt>; in principle, this should be possible also in the object inspectore, however, does not work at the time of this writing.
Line 53: Line 55:
 
* In the background the <tt>MapView</tt> component has established an internet connection to the default map provider and downloaded the default map. The maps are delivered as a series of '''tiled images''' at given size, usually 256x256 pixels, and usually in png format. They are stored in a cache directory (property <tt>CachePath</tt>) and used primarily to reduce internet traffic; only when an image is not found another download from the internet is triggered again.  
 
* In the background the <tt>MapView</tt> component has established an internet connection to the default map provider and downloaded the default map. The maps are delivered as a series of '''tiled images''' at given size, usually 256x256 pixels, and usually in png format. They are stored in a cache directory (property <tt>CachePath</tt>) and used primarily to reduce internet traffic; only when an image is not found another download from the internet is triggered again.  
  
===Magnification===
+
====Magnification====
 
* The map images are provided in different magnifications depending on the <tt>Zoom</tt> level of the <tt>MapView</tt>. Each step in <tt>Zoom</tt> level corresponds to doubling the magnification. The maximum <tt>Zoom</tt> level for most map providers is 18 -- this means that the most detailed maps cover a fraction of <tt>1/2^18</tt> of the earth circumference, about 150 m.
 
* The map images are provided in different magnifications depending on the <tt>Zoom</tt> level of the <tt>MapView</tt>. Each step in <tt>Zoom</tt> level corresponds to doubling the magnification. The maximum <tt>Zoom</tt> level for most map providers is 18 -- this means that the most detailed maps cover a fraction of <tt>1/2^18</tt> of the earth circumference, about 150 m.
 
* So, if you want to see '''more details''' you must increase the <tt>Zoom</tt> value of the <tt>MapView</tt>. You can enter the requested value in the Object Inspector, or add a <tt>TScrollbar</tt> or <tt>TTrackbar</tt> to the form to change the magnfication interactively by using the following simple <tt>OnChange</tt> event handler (set the <tt>Max</tt> of the bar to, say, 18:
 
* So, if you want to see '''more details''' you must increase the <tt>Zoom</tt> value of the <tt>MapView</tt>. You can enter the requested value in the Object Inspector, or add a <tt>TScrollbar</tt> or <tt>TTrackbar</tt> to the form to change the magnfication interactively by using the following simple <tt>OnChange</tt> event handler (set the <tt>Max</tt> of the bar to, say, 18:
Line 61: Line 63:
 
   MapView1.Zoom := TrackBar1.Position;
 
   MapView1.Zoom := TrackBar1.Position;
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
 +
* Alternatively, zooming can also be achieved by rotating the '''mouse wheel'''.
  
===Location===
+
====Location====
 
* By default the '''center of the map''' is the intersection of the 0-degree meridian with the equator - which is a bit off of the coast of western Africa. In order to focus onto a different location you must specify its longitude and latitude in the <tt>Center</tt> property of the <tt>MapView</tt>. This is a <tt>TRealPoint</tt> record having the longitude and latitude in the <tt>Lon</tt> and <tt>Lat</tt> record elements.  
 
* By default the '''center of the map''' is the intersection of the 0-degree meridian with the equator - which is a bit off of the coast of western Africa. In order to focus onto a different location you must specify its longitude and latitude in the <tt>Center</tt> property of the <tt>MapView</tt>. This is a <tt>TRealPoint</tt> record having the longitude and latitude in the <tt>Lon</tt> and <tt>Lat</tt> record elements.  
* Suppose we want to zoom into Manhattan. Use your favorite search engine to determine the geo coordinates of Manhattan: Longitude = -73.985130°, Latitude = 40.758896°. Alternatively you can also use the <tt>TGeoNames</tt> component which comes with the LazMapViewer package and provides access to a database of the geo coordinates of a huge number of locations via internet.
+
* Suppose we want to zoom into Manhattan. Use your favorite search engine to determine the geo coordinates of Manhattan: Longitude = -73.985130°, Latitude = 40.758896°. Alternatively you can also use the [[#Finding_Geo_Locations:_TMvGeoNames|<tt>TGeoNames</tt>]] component which comes with the LazMapViewer package and provides access to a database of the geo coordinates of a huge number of locations via internet.
 
<syntaxhighlight lang=Pascal>
 
<syntaxhighlight lang=Pascal>
 
procedure TForm1.FormActivate(Sender: TObject);
 
procedure TForm1.FormActivate(Sender: TObject);
Line 76: Line 79:
 
end; </syntaxhighlight>
 
end; </syntaxhighlight>
 
* In addition to specifying the location numerically the you can also change location by '''dragging the map with the mouse'''. Just press the left mouse button and slowly move the mouse in the direction where you want to see more - the map will follow. But you should be aware that usually neighboring maps will have to be loaded from the internet, and this makes the entire action a bit sluggish.
 
* In addition to specifying the location numerically the you can also change location by '''dragging the map with the mouse'''. Just press the left mouse button and slowly move the mouse in the direction where you want to see more - the map will follow. But you should be aware that usually neighboring maps will have to be loaded from the internet, and this makes the entire action a bit sluggish.
 +
 +
===Basic Documentation===
 +
[[file:tmapview 200.png|left]]
 +
==== Properties ====
 +
These are the main properties of the <tt>TMapView</tt> component:
 +
*<tt>Active (boolean)</tt>: must be set to <tt>true</tt> before the <tt>MapView</tt> component can display maps.
 +
*<tt>CacheOnDisk (boolean)</tt>: when set to <tt>true</tt> (default) dowloaded map tiles are stored in a directory to reduce internet traffic and for faster access. It is not recommended to turn this property off. The cache is not deleted automatically.
 +
*<tt>CachePath (string)</tt>: name of the directory in which downloaded map images are buffered.
 +
*<tt>DownloadEngine (TMvCustomDownloadEngine)</tt>: By default, the <tt>TFPHttpClient</tt> is employed to download the images from the map servers. If a different download engine is required the corresponding component can be hooked to this property. The standard installation of the ''LazMapViewer'' package contains the default <tt>TMvDEFPC</tt> downloader (based on FPC's <tt>TFPHttpClient</tt>) and the <tt>TMvDESynapse</tt> based on the ''Synapse'' library).
 +
*<tt>DrawingEngine (TMvCustomDrawingEngine)</tt>: Painting of the tiled images is a speed-critical task. By default, this is done using routines from the ''IntfGraphics'' unit; the corresponding drawing engine is in the <tt>TMvInfGraphicsDrawingEngine</tt> class. However, it is possible to provide a different drawing engine here. The standard installation of ''LazMapViewer'' comes with the alternative <tt>TMvRGBGraphicsDrawingEngine</tt> which is based on the ''RGBGraphics'' package, and with the <tt>TMvBGRADrawingEngine</tt> utilizing the ''BGRABitmap'' package.
 +
*<tt>MapProvider (string)</tt>: This string identifies the provider from which the maps are downloaded. Built-in providers allow to choose between ''OpenStreetMaps'', ''GoogleMaps'', ''Virtual Earth'', ''Yandex'' etc. Details are given in section [[#Map_Providers|Map Providers]].
 +
*<tt>UseThreads (boolean)</tt>: When set to <tt>true</tt> downloading and drawing of maps is delegated to several threads in order to achieve a smoother response. It is not recommended to turn this property off.
 +
*<tt>Zoom (integer)</tt>: Magnfication of the map: 0 = coarsest magnification, earth view, 17 or 18 = highest magnification. Each zoom step results in doubling of the magnfication factor.
 +
*<tt>ZoomToCursor (boolean)</tt>: When this is true zooming occurs relative to the mouse position, otherwise to the center of the map. This feature is interesing for zooming with the mouse wheel.
 +
 +
Here are properties related to overlayed GPS objects (points of interest, tracks):
 +
*<tt>GPSItems: TGPSObjectList</tt>: list of GPS objects assigned to the MapView See below for details.
 +
*<tt>DefaultTrackColor: TColor</tt>, <tt>DefaultTrackWidth: Integer</tt>: the default color and line width, respectively, of overlayed tracks. See below for details.
 +
*<tt>POIImage: TBitmap</tt>: bitmap which will be overlayed to identify a "point of interest" (POI).
 +
*<tt>POITextBgColor: TColor</tt>: background color of the overlayed text describing a point of interest. Turn off the text background by selecting <tt>clNone</tt>.
 +
 +
==== Events ====
 +
*<tt>OnCenterMove</tt>: fires whenever the <tt>Center</tt> of the <tt>MapView</tt> have been changed.
 +
*<tt>OnChange</tt>: fires whenever the <tt>Center</tt>, size or <tt>Zoom</tt> factor of the <tt>MapView</tt> change.
 +
*<tt>OnDrawGpsPoint</tt>: see below
 +
*<tt>OnZoomChange</tt>: fires whenever the <tt>Zoom</tt> factor of the <tt>MapView</tt> changes.
 +
*<tt>OnMouseDown, OnMouseEnter, OnMouseLeave, OnMouseMove, OnMouseUp</tt>: standard mouse events.
 +
 +
==== Main methods ====
 +
* <tt>procedure GetMapProviders(lstProviders: TStrings)</tt>: Returns in <tt>lstProviders</tt> the names of all registered map providers. When one of these strings is assigned to the <tt>MapProvider</tt> property of the <tt>MapVieew</tt> the servers of the corresponding provider will become the source of the displayed maps.
 +
* <tt>function GetVisibleArea: TRealArea</tt>: returns the top/left and bottom/right corner points of the displayed rectangle in geo coordinates. <tt>TRealArea</tt> is a record consisting of the <tt>TRealPoint</tt> records <tt>TopLeft</tt> and <tt>BottomRight</tt>.
 +
* <tt>function LonLatToScreen(aPt: TRealPoint): TPoint</tt>: maps a geo point (longitude, latitude) to screen pixels (relative to the <tt>TMapView</tt> instance).
 +
* <tt>function ScreenToLonLat(aPt: TPoint): TRealPoint</tt>: maps a the coordinates of a screen pixel (relative to the <tt>MapView</tt> instance) to geo coordinates (longitude, latitude). Here is an example how the geo coordinates of the mouse cursor can be displayed in two labels by means of the <tt>OnMouseMove</tt> event of the <tt>MapView</tt>:
 +
<syntaxhighlight lang=Pascal>
 +
uses
 +
  mvTypes,    // for TRealPoint
 +
  mvEngine;  // for LonToStr() and LatToStr() funtions
 +
 +
procedure TForm1.MapView1MouseMove(Sender: TObject; Shift: TShiftState;
 +
  X, Y: Integer);
 +
var
 +
  P: TRealPoint;
 +
begin
 +
  P := MapView1.ScreenToLonLat(Point(X, Y));
 +
  Label1.Caption := 'Longitude: ' + LonToStr(P.Lon, true);
 +
  Label2.Caption := 'Latitude: ' + LatToStr(P.Lat, true);
 +
end; 
 +
</syntaxhighlight>
 +
* <tt>procedure SaveToFile(AClass: TRasterImageClass; const AFileName: String)</tt>: Save the currently displayed map as bitmap of the given class to a file. Example for saving to a jpg image:
 +
<syntaxhighlight lang=Pascal>
 +
procedure TForm1.Button1Click(Sender: TObject);
 +
begin
 +
  MapView1.SaveToFile(TJpegImage, 'mapview.jpg');
 +
end;
 +
</syntaxhighlight>
 +
*<tt>procedure SaveToStream(AClass: TRasterImageClass; AStream: TStream)</tt>: Similar to <tt>SaveToFile</tt>, but output is not in a file, but in the stream provided.
 +
*<tt>function SaveToImage(AClass: TRasterImageClass): TRasterImage</tt>: creates an instance of the given image class and writes the currently visible view port image to it.
 +
*<tt>procedure CenterOnObj(obj: TGPSObj)</tt>: Centers the map on the provided GPS object. More on GPS objects below.
 +
*<tt>procedure ZoomOnArea(const aArea: TRealArea)</tt>: Adjusts the <tt>Zoom</tt> level such that the given area is completely shown and fills the component bounds as much as possible.
 +
*<tt>procedure ZoomOnObj(obj: TGPSObj)</tt>: Adjusts the <tt>Zoom</tt> level such that the given GPS object is completely seen at highest magnification. More about GPS objects below.
 +
 +
===Map Providers===
 +
The <tt>MapView</tt> component can display maps from various providers. To activate a given provider its name must be specified in the <tt>MapProvider</tt> property of the component.
 +
 +
Here is a list of the names of the built-in map providers:
 +
* OpenStreetMap Mapnik
 +
* OpenStreetMap Wikipedia
 +
* OpenStreetMap Sputnik
 +
* OpenStreetMap.fr Hot
 +
* OpenStreetMap.fr Cycle Map
 +
* Open Topo Map
 +
* Google Maps
 +
* Google Satellite
 +
* Yandex.Maps
 +
* Yandex.Maps Satellite
 +
* Virtual Earth Bing
 +
* Virtual Earth Aerial
 +
* Virtual Earth Hybrid
 +
 +
The following providers require an API key to access their service. The API key can be obtained by registering at the provider sites. Usually the API keys are free, however, a charge may be required for commercial usage - please see the details on the provider sites. The strings received as API keys must be stored in global variables of your application.
 +
 +
* Registration at https://www.thunderforest.com/docs/apikeys/, store API key in the variable <tt>ThunderForest_ApiKey</tt>:
 +
** Open Cycle Map
 +
** OpenStreetMap Transport
 +
* Registration at https://developer.here.com/?create=Freemium-Basic&keepState=true&step=account, store the received APP ID and APP CODE in the variables <tt>HERE_AppID</tt> and <tt>HERE_AppCode</tt>, respectively:
 +
** Here WeGo Map
 +
** Here WeGo Grey Map
 +
** Here WeGo Reduced Map
 +
** Here WeGo Transit Map
 +
** Here WeGo POI Map
 +
** Here WeGo Pedestrian Map
 +
** Here WeGo DreamWorks Map
 +
* Registration at https://home.openweathermap.org/users/sign_up, store the API Key in variable <tt>OpenWeatherMap_ApiKey</tt>:
 +
** OpenWeatherMap Clouds
 +
** OpenWeatherMap Precipitation
 +
** OpenWeatherMap Pressure
 +
** OpenWeatherMap Temperature
 +
** OpenWeatherMap Wind
 +
 +
=== GPS Objects ===
 +
The public <tt>TMapView</tt> property <tt>GPSItems</tt> collects data on points of interest and tracks to be overlayed on the map. The common data type of these overlay items is
 +
<syntaxhighlight lang=Pascal>
 +
type
 +
  TGPSObj = class
 +
    ...
 +
  public
 +
    destructor Destroy; override;
 +
    property Name: String read FName write FName;
 +
    property ExtraData: TObject read FExtraData write SetExtraData;
 +
    property IdOwner: Integer read FIdOwner;
 +
    property BoundingBox: TRealArea read GetBoundingBox write SetBoundingBox;
 +
  end;
 +
</syntaxhighlight>
 +
 +
The <tt>Name</tt> can be displayed near the item in the map.
 +
 +
<tt>ExtraData</tt> can be used freely, for example to define the color of a track. An example how this could be done is found in unit <tt>mvExtraData</tt>, classes <tt>TDrawingExtraData</tt> and <tt>TTrackExtraData</tt>.
 +
 +
<tt>IdOwner</tt> is the ID of the owner TGPSObj instance to which the current item belongs. <tt>BoundingBox</tt> gives the geo coordinates of the top/left and bottom/right corner points of the rectangle containing all points of the item.
 +
 +
==== Points ====
 +
A special <tt>TGPSObj</tt> item is a <tt>TGPSPoint</tt>, for example a scenic "Point-of-Interest" (POI), a way point along a hiking path etc.
 +
<syntaxhighlight lang=Pascal>
 +
  TGPSPoint = class(TGPSObj)
 +
  public
 +
    constructor Create(ALon,ALat: double; AEle: double=NO_ELE; ADateTime: TDateTime=NO_DATE);
 +
    class function CreateFrom(aPt: TRealPoint): TGPSPoint;
 +
 +
    function HasEle: boolean;
 +
    function HasDateTime: Boolean;
 +
    function DistanceInKmFrom(OtherPt: TGPSPoint; UseEle: boolean=true): double;
 +
 +
    property Lon: Double read GetLon;
 +
    property Lat: Double read GetLat;
 +
    property Ele: double read FEle;
 +
    property DateTime: TDateTime read FDateTime;
 +
    property RealPoint: TRealPoint read FRealPt;
 +
  end;
 +
</syntaxhighlight>
 +
In its constructor, the geo coordinate longitude and latitude must be specified. They are also available as read-only properties <tt>Lon</tt> and <tt>Lat</tt>. Optional parameters are the elevation of the point (property <tt>Ele</tt> as well as some date/time information (property <tt>DateTime</tt>), for example the date when this point was visited. A special function, <tt>DistanceInKmFrom</tt>, is available to calculate the on-earth distance from another GPS point.
 +
 +
==== Tracks ====
 +
The second important <tt>TGPSObj</tt> descendant is <tt>TGPSTrack</tt> which stores the points along a path, for example points visited on a trip or a hike. Each point is a instance of the <tt>TGPSPoint</tt> class, and the individual points are collected by the list <tt>Points</tt>. Again, there is a <tt>DataTime</tt> property usable for example to hold the date of the hike. Function <tt>TrackLengthInKm</tt> calculates the length of the entire path with the points being connected by straight line segments.
 +
<syntaxhighlight lang=Pascal>
 +
type
 +
  TGPSTrack = class(TGPSObj)
 +
  private
 +
    FDateTime: TDateTime;
 +
    FPoints: TGPSPointList;
 +
    function GetDateTime: TDateTime;
 +
  public
 +
    constructor Create;
 +
    destructor Destroy; override;
 +
 +
    procedure GetArea(out Area: TRealArea); override;
 +
    function TrackLengthInKm(UseEle: Boolean=true): double;
 +
 +
    property Points: TGPSPointList read FPoints;
 +
    property DateTime: TDateTime read GetDateTime write FDateTime;
 +
  end;
 +
</syntaxhighlight>
 +
 +
==== List of GPS Objects ====
 +
The property <tt>GpsItems</tt> of the <tt>TMapView</tt> component is the main class collecting all the GPS objects. It is an instance of the <tt>TGPSObjectList</tt>:
 +
<syntaxhighlight lang=Pascal>
 +
type
 +
  TGPSObjectList = class(TGPSObj)
 +
  ...
 +
    constructor Create;
 +
    destructor Destroy; override;
 +
 +
    Procedure Clear(OwnedBy: integer);
 +
    procedure ClearExcept(OwnedBy: integer; const ExceptLst: TIdArray; out Notfound: TIdArray);
 +
    procedure GetArea(out Area: TRealArea); override;
 +
    function GetObjectsInArea(const Area: TRealArea): TGPSObjList;
 +
    function GetIdsArea(const Ids: TIdArray; AIdOwner: integer): TRealArea;
 +
 +
    function Add(aItem: TGpsObj; AIdOwner: integer): integer;
 +
    procedure DeleteById(const Ids: Array of integer);
 +
 +
    procedure BeginUpdate;
 +
    procedure EndUpdate;
 +
 +
    property Count: integer read GetCount;
 +
    property Items[AIndex: Integer]: TGpsObj read GetItem; default;
 +
 +
    property OnModified: TModifiedEvent read FOnModified write FOnModified;
 +
  end;
 +
</syntaxhighlight>
 +
Objects can be added to the list by calling the <tt>Add</tt> method. Since each addition is accompanied by a repaint of the <tt>MapView</tt> you should call <tt>BeginUpdate</tt> before and <tt>EndUpdate</tt> after adding the objects. Every added object must be assigned to an arbitrary ID which is used to group the objects.
 +
 +
Method <tt>DeleteByID</tt> deletes all objects sharing the same IDE. <tt>Clear</tt>, on the other hand, clears all the objects which are owned by object having the specified ID. An extension is <tt>ClearExcept</tt> which does the same but skips IDs listed in the <tt>ExceptLst</tt> array.
 +
 +
<tt>GetObjectsInArea</tt> creates a TFPObjectlist (of type <tt>TGpsObjList</tt> here) with all the objects enclosed by the given rectangle (<tt>Area</tt>). <tt>GetIDsArea</tt>, conversely, returns the rectangle enclosing all the objects with the IDs listed in parameter <tt>IDs</tt> (which is a simple array here).
 +
 +
==== Example: Adding a Point of Interest ====
 +
Suppose you want to mark a special point in the map by a mouse click and identify it with some descriptive text. Write a handler for the OnMouseUp event in which you query the description and then take the pixel coordinates of the clicked point, convert it to geo coordinates and create a <tt>TGpsPoint</tt> from it which you add to the <tt>GpsItems</tt> of the <tt>MapView</tt>:
 +
<syntaxhighlight lang=Pascal>
 +
uses
 +
  mvTypes, mvGPSObj;
 +
 +
const
 +
  _POI_ = 10;
 +
 +
procedure TForm1.MapView1MouseUp(Sender: TObject; Button: TMouseButton;
 +
  Shift: TShiftState; X, Y: Integer);
 +
var
 +
  pt: TRealPoint;
 +
  gpsPt: TGpsPoint;
 +
  ptName: String;
 +
begin
 +
  ptName := '';
 +
  if InputQuery('Point of Interest', 'Enter name:', ptName) then
 +
  begin
 +
    pt := MapView1.ScreenToLonLat(Point(X, Y));
 +
    gpsPt := TGpsPoint.CreateFrom(pt, NO_ELE, Now() );
 +
    gpsPt.Name := ptName;
 +
    Mapview1.GPSItems.Add(gpsPt, _POI_);
 +
  end;
 +
end;
 +
</syntaxhighlight>
 +
 +
In this example the constant <tt>_POI_</tt> is used as ID of the added point. By default, the point is drawn as a red cross, the name is drawn underneath it. You can highlight the text by changing its background color in property <tt>POITextBgColor</tt> of the <tt>MapView</tt>. You can also replace the red cross by an arbitrary bitmap which must be loaded into the <tt>POIImage</tt> property (the folder ''example'' of the LazMapViewer installation contains a ready-to-use pin-cushion icon).
 +
 +
There is also an event, <tt>OnDrawGpsPoint</tt>, which can be used to replace the entire labeling process by custom routines. The event has the <tt>GpsPoint</tt> as parameter. Drawing style can be defined based on the data stored in the <tt>GpsPoint</tt>. The other event parameter is the <tt>DrawingEngine</tt> which provides methods for drawing; it will be explained below. Here is an example in which the added Point-of-Interest is drawn by a blue circle and labeled by a large bold font:
 +
<syntaxhighlight lang=Pascal>
 +
procedure TForm1.MapView1DrawGpsPoint(Sender: TObject;
 +
  ADrawer: TMvCustomDrawingEngine; APoint: TGpsPoint);
 +
const
 +
  R = 8;
 +
var
 +
  p: TPoint;
 +
begin
 +
  // Screen coordinates of the GPS point
 +
  p := MapView1.LonLatToScreen(APoint.RealPoint);
 +
 +
  // Draw a blue circle
 +
  ADrawer.PenColor := clBlack;
 +
  ADrawer.BrushColor := clBlue;
 +
  ADrawer.BrushStyle := bsSolid;
 +
  ADrawer.Ellipse(p.X-R, p.Y-R, P.X+R, p.Y+R);
 +
 +
  // Draw the point label
 +
  ADrawer.BrushStyle := bsClear;
 +
  ADrawer.FontName := 'SegoeUI';
 +
  ADrawer.FontSize := 16;
 +
  ADrawer.FontStyle := [fsBold];
 +
  ADrawer.TextOut(
 +
    p.X - ADrawer.TextWidth(APoint.Name) div 2,
 +
    p.Y + R + 4,
 +
    APoint.Name
 +
  );
 +
end;
 +
</syntaxhighlight>
 +
 +
==Finding Geo Locations: TMvGeoNames==
 +
<tt>TMvGeoNames</tt> is a component which searches the geo coordinates (longitude, latitude) of many cities or other locations in the world. Using the download engine of the <tt>MapView</tt> component it sends a query to the site http://geonames.org/search.html and analyzes the returned html string.
 +
 +
[[file:tmvgeonames 200.png|left]]
 +
===Getting Started===
 +
* Assuming that you already have set up an application for the <tt>TMapView</tt> component as described above, you simply drop a <tt>TMvGeoNames</tt> component on the form; it is located next to the <tt>TMapView</tt> icon in the ''Misc'' component palette.
 +
* Add a button which is supposed to trigger the search.
 +
* Write an <tt>OnClick</tt> handler for the button which calls the <tt>TMvGeoNames</tt> method <tt>Search</tt>. Enter the search destination as first parameter, and the download engine to be used as second parameter. The function returns the found longitude and latitude as a <tt>TRealPoint</tt> record which can be passed immediately to the <tt>Center</tt> property of the <tt>MapView</tt> component. This way, the viewer can immediately jump to the found location:
 +
<syntaxhighlight lang=pascal>
 +
procedure TForm1.Button1Click(Sender: TObject);
 +
begin
 +
  MapView1.Center := MvGeoNames1.Search('Grand Canyon National Park', MapView1.DownloadEngine);
 +
end;
 +
</syntaxhighlight>
 +
 +
===Documentation===
 +
====Methods====
 +
*<tt>function Search(ALocationName: String; ADownloadEngine: TMvCustomDownloadEngine): TRealPoint</tt>: searches the requested location using the specified download engine. Return value are the geo coordinates of the location given as a <tt>TRealPoint</tt>. The search returns also locations with similar names, however, they must be extracted by handling the <tt>OnNameFound</tt> event.
 +
 +
====Properties====
 +
*<tt>LocationName: string</tt>: Is the name of the (first) found location as listed by the ''geonames'' site.
 +
 +
====Events====
 +
*<tt>OnNameFound</tt>: This event is triggered while the received response of the ''geonames'' server is being analyzed. The html string usually contains a variety of locations. Whenver a location has been extracted from the received string the event is fired and tells the name, a description, and the longitude/latitude <tt>TRealPoint</tt> record for this location. Handling this event makes it possible to list all the found locations for example in a combobox or listbox to give the user the opportunity to select any one of them:
 +
<syntaxhighlight lang=pascal>
 +
type
 +
  TGeoLocation = class
 +
    Name: String;
 +
    Descr: String;
 +
    Coords: TRealPoint;
 +
  end;
 +
 +
procedure TForm1.MVGeoNames1NameFound(const AName: string;
 +
  const ADescr: String; const ALoc: TRealPoint);
 +
var
 +
  loc: TGeoLocation;
 +
begin
 +
  loc := TGeoLocation.Create;
 +
  loc.Name := AName;
 +
  loc.Descr := ADescr;
 +
  loc.Coords := ALoc;
 +
  ComboBox1.Items.AddObject(AName, loc);
 +
end; </syntaxhighlight>

Latest revision as of 00:20, 7 January 2021

lazmapviewer.png

About

LazMapViewer is a component for embedding maps obtained from the internet, such as Google maps or OpenStreetView, into a Lazarus form.

Authors

The initial version of the package was written by Maciej Kaczkowski and later improved by members of the Lazarus forum. The package is currently maintained by Werner Pamler (wp_xxyyzzz-at-gmx-dot-net).

License

Modified LGPL (with linking exception, like Lazarus LCL)

Download and Installation

Development version

Use an svn client to download the current trunk version from svn://svn.code.sf.net/p/lazarus-ccr/svn/components/lazmapviewer. Alternatively download the zipped snapshot from https://sourceforge.net/p/lazarus-ccr/svn/HEAD/tree/components/lazmapviewer/ and unzip it to some folder.

Release version

Occasionally, release versions are made available via the Online Package Manager for easy one-click-installation directly from Lazarus.

Installation

In Lazarus, go to "Package" > "Open Package File .lpk". Navigate to the folder with the LazMapViewer sources, and select lazmapviewerpkg.lkp. Click "Compile", then "Use" > "Install". This will rebuild the IDE (it may take some time). When the process is finished the IDE will restart, and you'll find the MapViewer in the palette Misc.

There are optional, supporting packages to extend the basic functionality. They must be installed in the same way, after lazmapviewerpkg.lpk.

Package file name contained components purpose
lazmapviewerpkb.lpk TMapView main component to display maps
TMvGeoNames access to geo coordinates of cities
TMvDEFpc default download engine using the internal fpc routines
lazmapviewer_synapse.lpk TMvDESynapse alternative download engine based on the Synapse library
lazmapviewer_rgbgraphics.lpk TMvRGBGraphicsDrawingEngine Alternative drawing engine based on the RGBGraphics package
lazmapviewer_bgra.lpk TMvBGRADrawingEngine Alternative drawing engine based on the BGRABitmap package

The Main Map Viewer: TMapView

Getting Started

Here is a short tutorial to create your first map.

Preparation

  • Drop a TMapView component on the form and size it as needed. The component is on the Misc tab of the Lazarus component palette.
  • Add this handler for the form's OnShow or OnActivate event and set the Active property of the MapView component to true; in principle, this should be possible also in the object inspectore, however, does not work at the time of this writing.
procedure TForm1.FormActivate(Sender: TObject);
begin
  MapView1.Active := true;
end;
  • That's all. Compile, run and see your fist map!
  • In the background the MapView component has established an internet connection to the default map provider and downloaded the default map. The maps are delivered as a series of tiled images at given size, usually 256x256 pixels, and usually in png format. They are stored in a cache directory (property CachePath) and used primarily to reduce internet traffic; only when an image is not found another download from the internet is triggered again.

Magnification

  • The map images are provided in different magnifications depending on the Zoom level of the MapView. Each step in Zoom level corresponds to doubling the magnification. The maximum Zoom level for most map providers is 18 -- this means that the most detailed maps cover a fraction of 1/2^18 of the earth circumference, about 150 m.
  • So, if you want to see more details you must increase the Zoom value of the MapView. You can enter the requested value in the Object Inspector, or add a TScrollbar or TTrackbar to the form to change the magnfication interactively by using the following simple OnChange event handler (set the Max of the bar to, say, 18:
procedure TForm1.TrackBar1Change(Sender: TObject);
begin
  MapView1.Zoom := TrackBar1.Position;
end;
  • Alternatively, zooming can also be achieved by rotating the mouse wheel.

Location

  • By default the center of the map is the intersection of the 0-degree meridian with the equator - which is a bit off of the coast of western Africa. In order to focus onto a different location you must specify its longitude and latitude in the Center property of the MapView. This is a TRealPoint record having the longitude and latitude in the Lon and Lat record elements.
  • Suppose we want to zoom into Manhattan. Use your favorite search engine to determine the geo coordinates of Manhattan: Longitude = -73.985130°, Latitude = 40.758896°. Alternatively you can also use the TGeoNames component which comes with the LazMapViewer package and provides access to a database of the geo coordinates of a huge number of locations via internet.
procedure TForm1.FormActivate(Sender: TObject);
var
  P: TRealPoint;
begin
  MapView1.Active := true;
  P.Lon := -73.985130;
  P.Lat := 40.758896;
  MapView1.Center := P;
end;
  • In addition to specifying the location numerically the you can also change location by dragging the map with the mouse. Just press the left mouse button and slowly move the mouse in the direction where you want to see more - the map will follow. But you should be aware that usually neighboring maps will have to be loaded from the internet, and this makes the entire action a bit sluggish.

Basic Documentation

tmapview 200.png

Properties

These are the main properties of the TMapView component:

  • Active (boolean): must be set to true before the MapView component can display maps.
  • CacheOnDisk (boolean): when set to true (default) dowloaded map tiles are stored in a directory to reduce internet traffic and for faster access. It is not recommended to turn this property off. The cache is not deleted automatically.
  • CachePath (string): name of the directory in which downloaded map images are buffered.
  • DownloadEngine (TMvCustomDownloadEngine): By default, the TFPHttpClient is employed to download the images from the map servers. If a different download engine is required the corresponding component can be hooked to this property. The standard installation of the LazMapViewer package contains the default TMvDEFPC downloader (based on FPC's TFPHttpClient) and the TMvDESynapse based on the Synapse library).
  • DrawingEngine (TMvCustomDrawingEngine): Painting of the tiled images is a speed-critical task. By default, this is done using routines from the IntfGraphics unit; the corresponding drawing engine is in the TMvInfGraphicsDrawingEngine class. However, it is possible to provide a different drawing engine here. The standard installation of LazMapViewer comes with the alternative TMvRGBGraphicsDrawingEngine which is based on the RGBGraphics package, and with the TMvBGRADrawingEngine utilizing the BGRABitmap package.
  • MapProvider (string): This string identifies the provider from which the maps are downloaded. Built-in providers allow to choose between OpenStreetMaps, GoogleMaps, Virtual Earth, Yandex etc. Details are given in section Map Providers.
  • UseThreads (boolean): When set to true downloading and drawing of maps is delegated to several threads in order to achieve a smoother response. It is not recommended to turn this property off.
  • Zoom (integer): Magnfication of the map: 0 = coarsest magnification, earth view, 17 or 18 = highest magnification. Each zoom step results in doubling of the magnfication factor.
  • ZoomToCursor (boolean): When this is true zooming occurs relative to the mouse position, otherwise to the center of the map. This feature is interesing for zooming with the mouse wheel.

Here are properties related to overlayed GPS objects (points of interest, tracks):

  • GPSItems: TGPSObjectList: list of GPS objects assigned to the MapView See below for details.
  • DefaultTrackColor: TColor, DefaultTrackWidth: Integer: the default color and line width, respectively, of overlayed tracks. See below for details.
  • POIImage: TBitmap: bitmap which will be overlayed to identify a "point of interest" (POI).
  • POITextBgColor: TColor: background color of the overlayed text describing a point of interest. Turn off the text background by selecting clNone.

Events

  • OnCenterMove: fires whenever the Center of the MapView have been changed.
  • OnChange: fires whenever the Center, size or Zoom factor of the MapView change.
  • OnDrawGpsPoint: see below
  • OnZoomChange: fires whenever the Zoom factor of the MapView changes.
  • OnMouseDown, OnMouseEnter, OnMouseLeave, OnMouseMove, OnMouseUp: standard mouse events.

Main methods

  • procedure GetMapProviders(lstProviders: TStrings): Returns in lstProviders the names of all registered map providers. When one of these strings is assigned to the MapProvider property of the MapVieew the servers of the corresponding provider will become the source of the displayed maps.
  • function GetVisibleArea: TRealArea: returns the top/left and bottom/right corner points of the displayed rectangle in geo coordinates. TRealArea is a record consisting of the TRealPoint records TopLeft and BottomRight.
  • function LonLatToScreen(aPt: TRealPoint): TPoint: maps a geo point (longitude, latitude) to screen pixels (relative to the TMapView instance).
  • function ScreenToLonLat(aPt: TPoint): TRealPoint: maps a the coordinates of a screen pixel (relative to the MapView instance) to geo coordinates (longitude, latitude). Here is an example how the geo coordinates of the mouse cursor can be displayed in two labels by means of the OnMouseMove event of the MapView:
uses
  mvTypes,    // for TRealPoint
  mvEngine;   // for LonToStr() and LatToStr() funtions

procedure TForm1.MapView1MouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
var
  P: TRealPoint;
begin
  P := MapView1.ScreenToLonLat(Point(X, Y));
  Label1.Caption := 'Longitude: ' + LonToStr(P.Lon, true);
  Label2.Caption := 'Latitude: ' + LatToStr(P.Lat, true);
end;
  • procedure SaveToFile(AClass: TRasterImageClass; const AFileName: String): Save the currently displayed map as bitmap of the given class to a file. Example for saving to a jpg image:
procedure TForm1.Button1Click(Sender: TObject);
begin
  MapView1.SaveToFile(TJpegImage, 'mapview.jpg');
end;
  • procedure SaveToStream(AClass: TRasterImageClass; AStream: TStream): Similar to SaveToFile, but output is not in a file, but in the stream provided.
  • function SaveToImage(AClass: TRasterImageClass): TRasterImage: creates an instance of the given image class and writes the currently visible view port image to it.
  • procedure CenterOnObj(obj: TGPSObj): Centers the map on the provided GPS object. More on GPS objects below.
  • procedure ZoomOnArea(const aArea: TRealArea): Adjusts the Zoom level such that the given area is completely shown and fills the component bounds as much as possible.
  • procedure ZoomOnObj(obj: TGPSObj): Adjusts the Zoom level such that the given GPS object is completely seen at highest magnification. More about GPS objects below.

Map Providers

The MapView component can display maps from various providers. To activate a given provider its name must be specified in the MapProvider property of the component.

Here is a list of the names of the built-in map providers:

  • OpenStreetMap Mapnik
  • OpenStreetMap Wikipedia
  • OpenStreetMap Sputnik
  • OpenStreetMap.fr Hot
  • OpenStreetMap.fr Cycle Map
  • Open Topo Map
  • Google Maps
  • Google Satellite
  • Yandex.Maps
  • Yandex.Maps Satellite
  • Virtual Earth Bing
  • Virtual Earth Aerial
  • Virtual Earth Hybrid

The following providers require an API key to access their service. The API key can be obtained by registering at the provider sites. Usually the API keys are free, however, a charge may be required for commercial usage - please see the details on the provider sites. The strings received as API keys must be stored in global variables of your application.

GPS Objects

The public TMapView property GPSItems collects data on points of interest and tracks to be overlayed on the map. The common data type of these overlay items is

type
  TGPSObj = class
    ...
  public
    destructor Destroy; override;
    property Name: String read FName write FName;
    property ExtraData: TObject read FExtraData write SetExtraData;
    property IdOwner: Integer read FIdOwner;
    property BoundingBox: TRealArea read GetBoundingBox write SetBoundingBox;
  end;

The Name can be displayed near the item in the map.

ExtraData can be used freely, for example to define the color of a track. An example how this could be done is found in unit mvExtraData, classes TDrawingExtraData and TTrackExtraData.

IdOwner is the ID of the owner TGPSObj instance to which the current item belongs. BoundingBox gives the geo coordinates of the top/left and bottom/right corner points of the rectangle containing all points of the item.

Points

A special TGPSObj item is a TGPSPoint, for example a scenic "Point-of-Interest" (POI), a way point along a hiking path etc.

  TGPSPoint = class(TGPSObj)
  public
    constructor Create(ALon,ALat: double; AEle: double=NO_ELE; ADateTime: TDateTime=NO_DATE);
    class function CreateFrom(aPt: TRealPoint): TGPSPoint;

    function HasEle: boolean;
    function HasDateTime: Boolean;
    function DistanceInKmFrom(OtherPt: TGPSPoint; UseEle: boolean=true): double;

    property Lon: Double read GetLon;
    property Lat: Double read GetLat;
    property Ele: double read FEle;
    property DateTime: TDateTime read FDateTime;
    property RealPoint: TRealPoint read FRealPt;
  end;

In its constructor, the geo coordinate longitude and latitude must be specified. They are also available as read-only properties Lon and Lat. Optional parameters are the elevation of the point (property Ele as well as some date/time information (property DateTime), for example the date when this point was visited. A special function, DistanceInKmFrom, is available to calculate the on-earth distance from another GPS point.

Tracks

The second important TGPSObj descendant is TGPSTrack which stores the points along a path, for example points visited on a trip or a hike. Each point is a instance of the TGPSPoint class, and the individual points are collected by the list Points. Again, there is a DataTime property usable for example to hold the date of the hike. Function TrackLengthInKm calculates the length of the entire path with the points being connected by straight line segments.

type
  TGPSTrack = class(TGPSObj) 
  private
    FDateTime: TDateTime;
    FPoints: TGPSPointList;
    function GetDateTime: TDateTime;
  public
    constructor Create;
    destructor Destroy; override;

    procedure GetArea(out Area: TRealArea); override;
    function TrackLengthInKm(UseEle: Boolean=true): double;

    property Points: TGPSPointList read FPoints;
    property DateTime: TDateTime read GetDateTime write FDateTime;
  end;

List of GPS Objects

The property GpsItems of the TMapView component is the main class collecting all the GPS objects. It is an instance of the TGPSObjectList:

type
  TGPSObjectList = class(TGPSObj)
  ...
    constructor Create;
    destructor Destroy; override;

    Procedure Clear(OwnedBy: integer);
    procedure ClearExcept(OwnedBy: integer; const ExceptLst: TIdArray; out Notfound: TIdArray);
    procedure GetArea(out Area: TRealArea); override;
    function GetObjectsInArea(const Area: TRealArea): TGPSObjList;
    function GetIdsArea(const Ids: TIdArray; AIdOwner: integer): TRealArea;

    function Add(aItem: TGpsObj; AIdOwner: integer): integer;
    procedure DeleteById(const Ids: Array of integer);

    procedure BeginUpdate;
    procedure EndUpdate;

    property Count: integer read GetCount;
    property Items[AIndex: Integer]: TGpsObj read GetItem; default;

    property OnModified: TModifiedEvent read FOnModified write FOnModified;
  end;

Objects can be added to the list by calling the Add method. Since each addition is accompanied by a repaint of the MapView you should call BeginUpdate before and EndUpdate after adding the objects. Every added object must be assigned to an arbitrary ID which is used to group the objects.

Method DeleteByID deletes all objects sharing the same IDE. Clear, on the other hand, clears all the objects which are owned by object having the specified ID. An extension is ClearExcept which does the same but skips IDs listed in the ExceptLst array.

GetObjectsInArea creates a TFPObjectlist (of type TGpsObjList here) with all the objects enclosed by the given rectangle (Area). GetIDsArea, conversely, returns the rectangle enclosing all the objects with the IDs listed in parameter IDs (which is a simple array here).

Example: Adding a Point of Interest

Suppose you want to mark a special point in the map by a mouse click and identify it with some descriptive text. Write a handler for the OnMouseUp event in which you query the description and then take the pixel coordinates of the clicked point, convert it to geo coordinates and create a TGpsPoint from it which you add to the GpsItems of the MapView:

uses
  mvTypes, mvGPSObj;

const
  _POI_ = 10;

procedure TForm1.MapView1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  pt: TRealPoint;
  gpsPt: TGpsPoint;
  ptName: String;
begin
  ptName := '';
  if InputQuery('Point of Interest', 'Enter name:', ptName) then
  begin
    pt := MapView1.ScreenToLonLat(Point(X, Y));
    gpsPt := TGpsPoint.CreateFrom(pt, NO_ELE, Now() );
    gpsPt.Name := ptName;
    Mapview1.GPSItems.Add(gpsPt, _POI_);
  end;
end;

In this example the constant _POI_ is used as ID of the added point. By default, the point is drawn as a red cross, the name is drawn underneath it. You can highlight the text by changing its background color in property POITextBgColor of the MapView. You can also replace the red cross by an arbitrary bitmap which must be loaded into the POIImage property (the folder example of the LazMapViewer installation contains a ready-to-use pin-cushion icon).

There is also an event, OnDrawGpsPoint, which can be used to replace the entire labeling process by custom routines. The event has the GpsPoint as parameter. Drawing style can be defined based on the data stored in the GpsPoint. The other event parameter is the DrawingEngine which provides methods for drawing; it will be explained below. Here is an example in which the added Point-of-Interest is drawn by a blue circle and labeled by a large bold font:

procedure TForm1.MapView1DrawGpsPoint(Sender: TObject;
  ADrawer: TMvCustomDrawingEngine; APoint: TGpsPoint);
const
  R = 8;
var
  p: TPoint;
begin
  // Screen coordinates of the GPS point
  p := MapView1.LonLatToScreen(APoint.RealPoint);

  // Draw a blue circle
  ADrawer.PenColor := clBlack;
  ADrawer.BrushColor := clBlue;
  ADrawer.BrushStyle := bsSolid;
  ADrawer.Ellipse(p.X-R, p.Y-R, P.X+R, p.Y+R);

  // Draw the point label
  ADrawer.BrushStyle := bsClear;
  ADrawer.FontName := 'SegoeUI';
  ADrawer.FontSize := 16;
  ADrawer.FontStyle := [fsBold];
  ADrawer.TextOut(
    p.X - ADrawer.TextWidth(APoint.Name) div 2,
    p.Y + R + 4,
    APoint.Name
  );
end;

Finding Geo Locations: TMvGeoNames

TMvGeoNames is a component which searches the geo coordinates (longitude, latitude) of many cities or other locations in the world. Using the download engine of the MapView component it sends a query to the site http://geonames.org/search.html and analyzes the returned html string.

tmvgeonames 200.png

Getting Started

  • Assuming that you already have set up an application for the TMapView component as described above, you simply drop a TMvGeoNames component on the form; it is located next to the TMapView icon in the Misc component palette.
  • Add a button which is supposed to trigger the search.
  • Write an OnClick handler for the button which calls the TMvGeoNames method Search. Enter the search destination as first parameter, and the download engine to be used as second parameter. The function returns the found longitude and latitude as a TRealPoint record which can be passed immediately to the Center property of the MapView component. This way, the viewer can immediately jump to the found location:
procedure TForm1.Button1Click(Sender: TObject);
begin
  MapView1.Center := MvGeoNames1.Search('Grand Canyon National Park', MapView1.DownloadEngine);
end;

Documentation

Methods

  • function Search(ALocationName: String; ADownloadEngine: TMvCustomDownloadEngine): TRealPoint: searches the requested location using the specified download engine. Return value are the geo coordinates of the location given as a TRealPoint. The search returns also locations with similar names, however, they must be extracted by handling the OnNameFound event.

Properties

  • LocationName: string: Is the name of the (first) found location as listed by the geonames site.

Events

  • OnNameFound: This event is triggered while the received response of the geonames server is being analyzed. The html string usually contains a variety of locations. Whenver a location has been extracted from the received string the event is fired and tells the name, a description, and the longitude/latitude TRealPoint record for this location. Handling this event makes it possible to list all the found locations for example in a combobox or listbox to give the user the opportunity to select any one of them:
type
  TGeoLocation = class
    Name: String;
    Descr: String;
    Coords: TRealPoint;
  end;

procedure TForm1.MVGeoNames1NameFound(const AName: string;
  const ADescr: String; const ALoc: TRealPoint);
var
  loc: TGeoLocation;
begin
  loc := TGeoLocation.Create;
  loc.Name := AName;
  loc.Descr := ADescr;
  loc.Coords := ALoc;
  ComboBox1.Items.AddObject(AName, loc);
end;