Skip to main content
Version: Latest
Technical ManualLooking for step-by-step instructions? In the User Manual: Territories and Maps

Geo, Maps and Territories

The Geo module (CrestApps.OrchardCore.Geo) gives modules a shared way to work with places. It references no vertical module, so field service, CRM, commerce or classifieds can all build on it. Its contracts are in CrestApps.OrchardCore.Geo.Abstractions and its services in CrestApps.OrchardCore.Geo.Core.

The admin screens are described in the User Manual: Territories and Maps.

Features​

FeatureIdDepends on
Geo, Maps and TerritoriesCrestApps.OrchardCore.GeoCrestApps Resources
Azure MapsCrestApps.OrchardCore.Geo.AzureMapsGeo
Google MapsCrestApps.OrchardCore.Geo.GoogleMapsGeo
OSRMCrestApps.OrchardCore.Geo.OsrmGeo

Points and providers​

GeoPoint is a WGS84 latitude and longitude (IsValid, GeoPoint.From(lat, lng)). Three provider seams are chosen by name in the site settings:

ContractDoes
IGeocodingProviderGeocodeAsync(GeocodeQuery) returns GeocodeCandidates (point, confidence, formatted address, postal code, country); ReverseGeocodeAsync(GeoPoint) does the reverse.
ITravelTimeProviderGetMatrixAsync(origins, destinations, departureUtc, TravelOptions) returns a TravelMatrix of TravelLegs (duration and meters).
IRouteProviderGetRouteAsync(points) returns a GeoRoute to draw.

IGeoService is what modules call. It uses the configured providers and:

  • Geocoding throws the provider's errors so callers can retry, and returns a response with no provider when none is set up.
  • Travel times are cached in the distributed cache (points rounded to about 10 meters, departures to the quarter hour, kept for Keep travel times) and fall back to the built-in estimate when the provider is missing or fails.
  • Routes fall back to straight lines.

The built-in Haversine provider (HaversineTravelTimeProvider) is always registered: the great-circle distance (GeoMath.DistanceMeters) times the road factor (1.3), at the average speed (40 km/h) or TravelOptions.SpeedKmh, such as a territory's.

Providers​

Each provider is its own module and feature, adds its section to Settings > Maps and Geocoding, and is then chosen there. Keys are kept encrypted with data protection and never shown again. A provider throws GeoProviderException when it has no key or address, when its key is refused, or when it finds no route between two points; IGeoService then falls back to the built-in estimate for travel times and routes, and the geocoding queue retries.

ProviderGeocodingTravel timesRoutesNotes
Azure Maps (AzureMaps)Search geocoding and reverse geocoding (2023-06-01)The synchronous route matrix, with traffic, 100 cells a requestRoute directions with text instructionsThe key goes in the subscription-key header.
Google Maps (GoogleMaps)The Geocoding API, with the location type as confidenceThe Distance Matrix, with traffic, split into 25 by 25 and 100 elements a requestThe Directions API, its encoded polyline decoded by PolylineEncodingMaxCacheDuration is zero: its travel times are never cached, as its terms require. ZERO_RESULTS is an empty answer; every other status is a refusal.
OSRM (Osrm)—The table serviceThe route service with steps written as sentencesA self-hosted server's address and profile; no key.

ITravelTimeProvider.MaxCacheDuration (default null) caps how long IGeoService keeps a provider's answers; DisplayName names a provider in the settings. Each provider's HTTP client uses the standard resilience handler (retries, timeouts and a circuit breaker). The tests check every provider against the same contract with responses written from each service's documented format.

Choosing a geocoding provider wakes the addresses that waited for one (IGeocodeQueue.WakeAsync).

Service territories​

A ServiceTerritory has a name, an optional parent, a shape (GeoJSON Polygon or MultiPolygon, as a geometry, Feature or FeatureCollection), postal codes (a code ending with * matches every code that starts with the rest), a time zone, a travel speed and Members (teams or people by the identifiers of the modules that own them, Primary or Secondary). The handler tidies postal codes, checks the shape can be read (GeoShape.TryParse) and refuses a territory inside itself.

ITerritoryResolver.ResolveAsync(point, postalCode) (and the pure TerritoryMatcher.Match) finds the territory with the smallest shape the point is in; otherwise the territory with the postal code, an exact code before the longest prefix. The TerritoryMatch says which matched.

Geocoding in the background​

IGeocodeQueue.EnqueueAsync(targetType, targetId, query) keeps one GeocodeRequest per record, replacing an earlier one. The Geocode addresses background task runs every minute (ProcessDueAsync):

  • A place found is written back through the record type's IGeocodeTarget, best candidate first.
  • A provider error is retried after 1, 5, 30 and 120 minutes, then given up after five attempts.
  • No match, or a record that is gone, is given up at once.
  • With no provider set up, requests wait an hour at a time without counting an attempt.

The settings page shows how many addresses are waiting, geocoded and not found, and the latest problem.

Workforce registers targets when Geo is on: a team's location and a worker's home base are geocoded when they have an address and no coordinates. Changing a home base address clears its old coordinates.

Addresses​

Address gains optional Latitude, Longitude, GeocodedUtc and GeocodeSource (a provider's name, or Manual when pinned), copied by Clone, for the modules that geocode their addresses.

Map component​

CrestApps Resources vendors Leaflet 1.9.4, Leaflet.markercluster 1.5.3 and Leaflet-Geoman free 2.20.4 (all MIT) as the leaflet, leaflet-markercluster (with leaflet-markercluster-default) and leaflet-geoman scripts and styles, and the crestapps-map wrapper:

const map = window.crestappsMap.create('my-map'); // reads data-tile-url, data-attribution, data-latitude, data-longitude, data-zoom
window.crestappsMap.showShapes(map, [{ label, geoJson }]); // read-only shapes, fitted
window.crestappsMap.addMarkers(map, [{ latitude, longitude, label }], { fit: true });
window.crestappsMap.pin(map, latitudeInput, longitudeInput); // a draggable pin that writes two inputs
window.crestappsMap.editShapes(map, textarea); // Geoman drawing kept as a GeoJSON FeatureCollection

The Geo module's _GeoMap partial renders a map element with the site's tile settings and the scripts. Tiles are requested with the site's origin as referrer, which tile servers such as OpenStreetMap's require.

Settings​

Settings > Maps and Geocoding (GeoSettings, permission Manage maps and geocoding settings): the geocoding, travel-time and route providers, the built-in estimate's average speed and road factor, how long travel times are cached, the tile address and attribution, and where maps open. The default tile server is OpenStreetMap's, for trying things out only: a live site must use its own tiles or a commercial tile service, under the OpenStreetMap tile usage policy.

Recipes and deployment​

The Geo recipe step creates or updates service territories by ItemId (Territories), parents first, with the same checks as the editor; the Service territories deployment step exports them. The Maps and geocoding settings deployment step exports GeoSettings (the providers chosen, the built-in estimate and the tiles) through Orchard Core's Settings step; provider keys stay on each site.

Permissions​

PermissionAllows
ManageGeoTerritories (Manage service territories)The territory list, map, finder and editor.
ManageGeoSettings (Manage maps and geocoding settings)The Maps and Geocoding settings.

Territory members​

A territory's Members say who serves it. The territory's page lists them through every ITerritoryMemberSource (a Kind, a label and the choices); Dispatch adds technicians and teams. Members of a kind no module lists any more are kept as they are.