Plateforme Level Extreme
Abonnement
Profil corporatif
Produits & Services
Support
Légal
English
Hiring a Mapping Guru!!
Message
De
10/06/2020 05:07:49
Antonio Lopes (En ligne)
BookMARC
Coimbra, Portugal
 
 
Information générale
Forum:
Visual FoxPro
Catégorie:
Emplois
Divers
Thread ID:
01674617
Message ID:
01674815
Vues:
81
J'aime (4)
>Imagine a driver for a flower shop has given a list of deliveries (in a DBF file). Now, he needs to plan his trip and start delivering.
>He would need these addresses be put on the map first and then the most efficient Route be calculated by putting numbers next to each delivery, as to which one should be done first delivery, and second, and third. And so on...
>
>The trip always starts from Shop and ends going back to the shop.
>So I am just talking about mapping of addresses in a dbf file.

Ok, Cyrus, this will take a bit long, but hopefully, it will turn to be a pleasant journey (kind of pun intended).

I'll use the overHere library, a VFPX project found at https://github.com/atlopes/overHere. The library consists of an SDK that wraps access to the Here.com REST API location platform. You'll need to acquire a (free) API Key for your REST projects at the Here Developer portal. And then you're ready to go.

What follows is a demo close to your use case: we want to plan a visit to our Northwind customers in London after we land at Heathrow Airport. We'll be ready to start by tomorrow at 9:00 AM, and we expect to take about half-hour visits at each customer. When the turn is complete, we want to return to Heathrow Airport to our journey back home.

1. Load the library

You can find the general loader at the [source/api] folder. Insert your Here API Key as the parameter of the SetCredentials() method, and you can proceed.
* load the libraries
DO LOCFILE("here_loader.prg")

* instantiate an overHere manager
LOCAL oh AS overHere

m.oh = CREATEOBJECT("overHere")
m.oh.SetCredentials(YOUR-API-KEY-HERE)
2. Get the coordinates of the addresses

We'll use the GeocodeSearch overHere resource to get the geographic coordinates of the addresses we want to visit.

In a real application, you should take care of not found coordinates, as well as multiple coordinates for an address. Eventually, you could also take into account weak responses. In this demo, we'll use only the first response for each address, and ignore all the rest.

We'll store the found coordinates in a cursor for later use. In a real application (again), coordinates would preferably be stored close to the contact record, and updated after edition of the address.

The first two addresses are of the Heathrow Airport; the others come from the Northwind database and are stored as the SELECT statement retrieves them.
* first, let's get some addresses from the NorthWind database

SELECT * ;
	FROM (HOME(0) + "Samples\Northwind\customers") ;
	WHERE City == "London" AND Country == "UK" ;
	INTO CURSOR curCustomers

CREATE CURSOR curWaypoints ;
	(WPId Varchar(20), ;
	Latitude Double (4), Longitude Double (4), ;
	Sequence Integer NULL DEFAULT NULL, ;
	Arrival Datetime NULL DEFAULT NULL, Departure Datetime NULL DEFAULT NULL)

* and try to find their geo coordinates

LOCAL GC AS oh_GeocodeSearch

m.GC = m.oh.SetResource("GeocodeSearch")

* we start by locating the Heathrow Airport

m.GC.SearchText.Set("UK Heathrow Airport")

IF !ISNULL(m.GC.GetLocation(.T.)) AND m.GC.Location.Views.Count > 0
	INSERT INTO curWaypoints (WPId, Latitude, Longitude) ;
		VALUES ('HEATHROW0', ;
			m.GC.Location.Views(1).Results(1).Location.NavigationPosition.Latitude.Get(), ;
			m.GC.Location.Views(1).Results(1).Location.NavigationPosition.Longitude.Get())
	INSERT INTO curWaypoints (WPId, Latitude, Longitude) ;
		VALUES ('HEATHROW1', ;
			m.GC.Location.Views(1).Results(1).Location.NavigationPosition.Latitude.Get(), ;
			m.GC.Location.Views(1).Results(1).Location.NavigationPosition.Longitude.Get())
ELSE
	? "Couldn't locate starting point, aborting..."
	RETURN
ENDIF

* and now the customers' addresses location

SELECT curCustomers

SCAN
	m.GC.SearchText.Set(TEXTMERGE("<<TRIM(curCustomers.city)>> <<TRIM(curCustomers.address)>>"))
	IF !ISNULL(m.GC.GetLocation(.T.)) AND m.GC.Location.Views.Count > 0
		INSERT INTO curWaypoints (WPId, Latitude, Longitude) ;
			VALUES (curCustomers.customerid, ;
				m.GC.Location.Views(1).Results(1).Location.NavigationPosition.Latitude.Get(), ;
				m.GC.Location.Views(1).Results(1).Location.NavigationPosition.Longitude.Get())
	ELSE
		? "Not found:", TRIM(curCustomers.address)
	ENDIF
ENDSCAN
3. Find the visit sequence

We'll use the FindSequence overHere resource to establish the order of waypoints in our tour.

The parametrization can get quite complicated. For instance, you may set the type of vehicle you're using, the service and rest times, any specific appointment arrangements, and so on.

In this demo, we know the hour of departure, we'll drive a car, and we'll take 30 minutes of service time at each visit. And, of course, we have a start and ending point.
* by now, we have the starting point (Heathrow Airport) and the known addresses of our customers in London

* let's find the best visit sequence

LOCAL FS AS oh_FindSequence
LOCAL WP AS oh_WaypointWithIdType
LOCAL WPF AS oh_FindWaypointInfoType

m.FS = m.oh.SetResource("FindSequence")

* we'll go by car

m.FS.Mode.Set("Type", "fastest")
m.FS.Mode.Set("Transport", "car")
m.FS.Mode.Set("TrafficMode", "enabled")
m.FS.ImproveFor.Set("time")

SELECT curWaypoints

GO TOP

* where do we start

m.FS.Start.Set(curWaypoints.wpid , curWaypoints.latitude, curWaypoints.longitude)

* we also want to go back to Heathrow, after our visits

SKIP

m.FS.End.Set(curWaypoints.wpid, curWaypoints.latitude, curWaypoints.longitude)

* when we'll start (tomorrow, at 09:00)

m.FS.Departure.Set(DTOT(DATE() + 1) + 9 * 3600)

SKIP

SCAN REST

	* add all the customers addresses
	m.WP = CREATEOBJECT("oh_WaypointWithIdType")
	m.WP.Set(curWaypoints.wpid, curWaypoints.latitude, curWaypoints.longitude)
	* let's assume our visits will take 30 minutes each
	m.WP.ServiceTime.Set(30 * 60)
	m.FS.Destinations.Add(m.WP)

ENDSCAN

* find the sequence
m.FS.Find(.T.)
4. Get a route for the sequence

We'll use the CalculateRoute overHere resource to get a route that will pass through all the waypoints sequenced previously.

We'll try to mimic the conditions of the sequence to get proper directions and a map definition to use for display.
* we have a sequence, now we need a route for it

LOCAL CR AS oh_CalculateRoute
LOCAL GC AS oh_GeoWaypointParameterType

m.CR = m.oh.SetResource("CalculateRoute")

* copy the settings from the find sequence resource
* to get consistent directions

m.CR.Mode.Set("Type", m.FS.Mode.Get())
m.CR.Mode.Set("Transport", m.FS.Mode.TransportMode.Get())
m.CR.Mode.Set("TrafficMode", m.FS.Mode.TrafficMode.Get())
m.CR.Departure.Set(m.FS.Departure.Get())

* use the waypoints in the sequence to define the route

* contrary to the find sequence resource, calculate route will respect
* the waypoints in the order they are added

FOR EACH m.WPF IN m.FS.Sequence.Results(1).Waypoints

	* register the sequence of waypoints for browsing, later
	UPDATE curWaypoints ;
		SET Sequence = m.WPF.Sequence.Get(), ;
			Arrival = m.WPF.EstimatedArrival.Get(), ;
			Departure = m.WPF.EstimatedDeparture.Get() ;
		WHERE WPId == m.WPF.Id.Get()

	* add the waypoint to the route we want to calculate
	m.GC = CREATEOBJECT("oh_GeoWaypointParameterType")
	m.GC.Set(,,m.WPF.Latitude.Get(), m.WPF.Longitude.Get(),,,m.WPF.Id.Get())
	m.CR.Waypoints.Add(m.GC)

ENDFOR

* just some typical settings
m.CR.Representation.Set("display")
m.CR.ManeuverAttrib.Parse("po,le,tt,bb")
m.CR.InstructionFormat.Set("text")

m.CR.Calculate(.T.)
5. Display the route

We'll use the RouteMap overHere resource to display the calculated route.

The calculation loads the response with lots of details, including maneuver instructions. We'll disregard these details, as we are only interested in display the route map, with the customers' addresses marked along the route.
* the route is calculated, by now

* although we have lots of details in the calculated route
* we just want to draw a map of it

LOCAL RM AS oh_RouteMap

m.RM = m.oh.SetResource("RouteMap")

LOCAL Route AS oh_MapRouteMarkerGroupType
LOCAL Pos AS oh_GeoCoordinateType
LOCAL RouteShape AS oh_GeoPolylineType

m.Route = CREATEOBJECT("oh_MapRouteMarkerGroupType")
* get the shape of the route from the response 
m.Route.RouteCoords.Parse(m.CR.Route.Routes.Item(1).Shape.ToString())

* set it, it's almost ready to display

m.RM.Routes.Add(m.Route)

* back to our waypoints, we want to display markers along the route
FOR EACH m.WPF IN m.FS.Sequence.Results(1).Waypoints
	m.POI = CREATEOBJECT("oh_PointOfInterestType")
	m.POI.Set(m.WPF.Latitude.Get(), m.WPF.Longitude.Get(), ;
		"ffB0B0", "004040", 10, m.WPF.Sequence.ToString() + " " + m.WPF.Id.Get())
	m.RM.PointsOfInterest.Add(m.POI)
ENDFOR

* get the map of the route
m.RM.Request()

IF TYPE("_Screen.oh") == "U"
	_Screen.AddObject("oh", "Image")
	_Screen.oh.Visible = .T.
ENDIF

* and display it in the VFP screen

_Screen.oh.PictureVal = m.RM.GetImage()

* browse through the unordered and ordered sequence of points
* (the map will be left behind)

SELECT curWaypoints
GO TOP
BROWSE NOWAIT LAST

SELECT * FROM curWaypoints ORDER BY Sequence INTO CURSOR curOrderedWaypoints

SELECT curOrderedWaypoints
GO TOP
BROWSE NOWAIT LAST
----------------------------------
António Tavares Lopes
Précédent
Suivant
Répondre
Fil
Voir

Click here to load this message in the networking platform