Situm Dashboard allows you to define the wayfinding paths in your building. Based on this information, Situm SDK is able to compute the route from any point A to any point B. This section will teach you the basics about routing.
Routing concepts #
Before we dive in, it will be useful to understand a few concepts first. In Situm, wayfinding paths are configured in Situm Dashboard as a graph, where the edges represent all the passable paths in the building (and the nodes are just the start/end of each edge).
Each edge can have a number of properties (more info here): they can be accessible (suitable for people in wheelchairs, e.g. they avoid elevators), they can represent one-way or two way paths, they can interconnect different floors (representing elevators, escalators, etc.), etc.
When this graph has been configured, Situm SDK is able to compute the route between any two points within the same venue (A and B). Conceptually, this route will be composed of a set of data structures.
Structure | Description | More info |
---|---|---|
Points (nodes) | The set of ordered (consecutive) graph nodes that the route traverses | Computing your first route |
Steps (edges) | The set of ordered (consecutive) graph edges that connect those nodes. | Extracting route points |
Segment | A list of consecutive points that are in the same floor. | Extracting route steps (edges) |
Computing your first route #
Computing a route with Situm SDK can be break down in 4 steps:
- Determine the building identifier of the building where you want to retrieve the route.
- Retrieve the information of that building.
- Define the points A & B. For each of them, you will need to specify: its floor identifier, its WSG84 coordinates (latitude and longitude).
- Create the DirectionsRequest object (Android, iOS, Cordova, ReactNative), that defines how the route should be computed: origin, destination, whether it should avoid non accessible paths, etc.
- Compute the route using the DirectionsManager (or in Situm jargon, requestDirections: Android, iOS, Cordova, ReactNative)!
Internally, the route computation process goes like this:
- Among all the edges, finds the one that is closer to point A. Then, proceeds to find the exact point in that edge that is closer to A. Let’s call this point A’.
- Performs the same process for point B. Let’s call the resulting point B’.
- Computes the shortest route between points A’ and B’.
- Generates a route that goes from point A to A’ (a straight line), from point A’ to B’ (computed in step 3), and finally from point B’ to B (another straight line).
The output will be a Route object (Android, iOS, Cordova, ReactNative) that will contain all the relevant information: route nodes or points, edges, segments, indications, etc. The following example shows an Android simple example that shows you how to do it.
package com.example.helloworld; import androidx.appcompat.app.AppCompatActivity; import es.situm.sdk.location.util.CoordinateConverter; import android.os.Bundle; import android.util.Log; import es.situm.sdk.SitumSdk; import es.situm.sdk.directions.DirectionsRequest; import es.situm.sdk.error.Error; import es.situm.sdk.model.cartography.BuildingInfo; import es.situm.sdk.model.cartography.Point; import es.situm.sdk.model.directions.Indication; import es.situm.sdk.model.directions.Route; import es.situm.sdk.model.directions.RouteStep; import es.situm.sdk.model.location.Coordinate; import es.situm.sdk.utils.Handler; public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName(); private CoordinateConverter coordinateConverter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); SitumSdk.init(this); //We will compute routes in building 10194 String buildingId="10194"; //First of all, we need to retrieve the information of the building ... SitumSdk.communicationManager().fetchBuildingInfo(buildingId, new Handler<BuildingInfo>() { @Override public void onSuccess(BuildingInfo buildingInfo) { //We define the data points A & B between which we will compute the route String floorIdA="29685"; Coordinate coordinateA = new Coordinate(43.3434333899059, -8.42761197887612); String floorIdB="29686"; Coordinate coordinateB = new Coordinate(43.3451052729103, -8.42827590527984); // With this information, we build the coordinate converter ... coordinateConverter = new CoordinateConverter(buildingInfo.getBuilding().getDimensions(),buildingInfo.getBuilding().getCenter(),buildingInfo.getBuilding().getRotation()); // ... which allows us to build the 2 points (A & B) indicating both the global & cartesian coordinates Point pointA = new Point(buildingId, floorIdA, coordinateA, coordinateConverter.toCartesianCoordinate(coordinateA) ); Point pointB = new Point(buildingId, floorIdB, coordinateB, coordinateConverter.toCartesianCoordinate(coordinateB) ); // The DirectionsRequest allows us to configure the route calculation: source point, destination point, other options... DirectionsRequest directionsRequest = new DirectionsRequest.Builder().from(pointA, null).to(pointB).build(); //Finally, we compute the route SitumSdk.directionsManager().requestDirections(directionsRequest, new Handler<Route>() { @Override public void onSuccess(Route route) { Log.i(TAG, "Computed route " + route); } @Override public void onFailure(Error error) { Log.e(TAG, "Error" + error); } }); } @Override public void onFailure(Error error) { Log.e(TAG, "Error " + error); } }); } }
You may modify the appropriate variables to define your own A&B points. Then, if you execute the previous example, you will get a log similar to this:
I/MainActivity: Computed route Route{request=DirectionsRequest{from=Point{buildingIdentifier='10194', floorIdentifier='29685', cartesianCoordinate=CartesianCoordinate{x=134,90, y=160,15}, coordinate=Coordinate{latitude=43,343433, longitude=-8,427612}, isOutdoor=false}, bearingFrom=null, to=Point{buildingIdentifier='10194', floorIdentifier='29686', cartesianCoordinate=CartesianCoordinate{x=324,82, y=123,66}, coordinate=Coordinate{latitude=43,345105, longitude=-8,428276}, isOutdoor=false}, accessibilityMode=CHOOSE_SHORTEST, minimizeFloorChanges=false, options: es.situm.sdk.directions.DirectionsOptions@1876eef}, ... points=[Point{buildingIdentifier='10194', floorIdentifier='29685', cartesianCoordinate=CartesianCoordinate{x=134,90, y=160,15}, coordinate=Coordinate{latitude=43,343433, longitude=-8,427612}, isOutdoor=false}, DefaultNode{id=-1} Point{buildingIdentifier='10194', floorIdentifier='29685', cartesianCoordinate=CartesianCoordinate{x=146,26, y=159,26}, coordinate=Coordinate{latitude=43,343528, longitude=-8,427666}, isOutdoor=false}, DefaultNode{id=43} Point{buildingIdentifier='10194', floorIdentifier='29685', cartesianCoordinate=CartesianCoordinate{x=143,13, y=119,11}, coordinate=Coordinate{latitude=43,343667, longitude=-8,427207}, isOutdoor=false}, DefaultNode{id=51} ....
Success! You have computed your first route. What should you do next?
Extracting route points #
We’ve seen that a route is essentially a set of ordered nodes, connected by edges, that go from point A to B. In the previous log output, you may have noticed that we’ve printed out a set of points. These are the nodes of the route in the right order from the beginning (point A) to the end (point B).
Situm SDK allows you to extract a list of route points (Android, iOS, Cordova, ReactNative). For instance, in Android you may do:
... SitumSdk.directionsManager().requestDirections(directionsRequest, new Handler<Route>() { @Override public void onSuccess(Route route) { for (Point point: route.getPoints()){ Log.i(TAG, "Point's building ID " + point.getBuildingIdentifier()); Log.i(TAG, "Point's floor ID " + point.getFloorIdentifier()); Log.i(TAG, "Point's Latitude " + point.getCoordinate().getLatitude() + " Longitude " + point.getCoordinate().getLongitude()); Log.i(TAG, "Point's Cartesian X " + point.getCartesianCoordinate().getX() + " Cartesian Y " + point.getCartesianCoordinate().getY()); Log.i(TAG, "-----"); } } ... }
After execution, you will see the following log:
I/MainActivity: Point's building ID 10194 Point's floor ID 29686 Point's Latitude 43.344254545619556 Longitude -8.427709954367506 Point's Cartesian X 219.771 Cartesian Y 125.749 ----- Point's building ID 10194 Point's floor ID 29686 Point's Latitude 43.34426453022196 Longitude -8.427803794047842 Point's Cartesian X 224.218 Cartesian Y 132.022 ----- ...
Each point contains the relevant location information (building identifier, floor identifier, coordinates, etc.). Take a look at the Point Android reference to have an idea of the nature of this information.
Extracting route steps (edges) #
In the previous example, we’ve seen how to extract the ordered nodes or points that conform a route. What about the steps or edges connecting those points? Situm SDK allows to retrieve them as well (Android, iOS, Cordova, not available in ReactNative). For instance, in Android you may do:
... SitumSdk.directionsManager().requestDirections(directionsRequest, new Handler<Route>() { @Override public void onSuccess(Route route) { for (RouteStep step : route.getSteps()) { Log.i(TAG, "Step Id: " + step.getId()); Log.i(TAG, "Distance (meters) to end of route: " + step.getDistanceToGoal()); Log.i(TAG, "Distance (meters) to next floor change: " + step.getDistanceToFloorChange()); Log.i(TAG, "Length (meters) of this edge/step: " + step.getDistance()); Point from = step.getFrom(); Point to = step.getTo(); Log.i(TAG, "FromPoint: BuildingId "+ from.getFloorIdentifier() + " FloorId " + from.getFloorIdentifier() + " Latitude "+ from.getCoordinate().getLatitude() + " Longitude " + from.getCoordinate().getLongitude()); Log.i(TAG, "ToPoint: BuildingId "+ to.getFloorIdentifier() + " FloorId " + to.getFloorIdentifier() + " Latitude "+ to.getCoordinate().getLatitude() + " Longitude " + to.getCoordinate().getLongitude()); Log.i(TAG, "----"); } ... }
After execution, you will see the following log:
I/MainActivity: Step Id: 1 Distance (meters) to end of route: 283.61143215971987 Distance (meters) to next floor change: 55.85009662344499 Length (meters) of this edge/step: 11.391021886334167 FromPoint: BuildingId 10194 FloorId 29685 Latitude 43.3434333899059 Longitude -8.42761197887612 ToPoint: BuildingId 10194 FloorId 29685 Latitude 43.34352806065825 Longitude -8.427665920512453 ---- Step Id: 2 Distance (meters) to end of route: 272.2204102733857 Distance (meters) to next floor change: 44.459074737110825 Length (meters) of this edge/step: 40.271730829572306 FromPoint: BuildingId 10194 FloorId 29685 Latitude 43.34352806065825 Longitude -8.427665920512453 ToPoint: BuildingId 10194 FloorId 29685 Latitude 43.343667245037864 Longitude -8.427207329649177 ---- ...
Each step contains the relevant location information: origin point (“from”), endpoint (“to”), several pre-computed distances (length of edge, distance to next floor change, distance to end destination), etc.
Extracting route segments (set of steps within the same floor) #
The most common operation we might want to perform with a route computed by Situm SDK is to visualize it on top of the building floorplans.
One way to do this is to draw a polyline that traverses all the route points, retrieved as shown in the previous sections. Usually, we will visualize just one floorplan at a time, but the list of points as retrieved in the previous sections contains points across all floors traversed by the route. Therefore, we need to:
- Filter out the data points so we just visualize those contained in the floor that we want to show.
- Make sure we don’t incorrectly connect route points when visualizing them. Sometimes, a route may start in a certain floor (e.g. floor 0), go through other floor(s), only to come back to the original floor. A common mistake is to just throw all the “floor 0” points in a list and draw the polyline: therefore, the polyline will incorrectly join points that should not be tied together.
Situm facilitates these operations by providing a way to retrieve segments (Android, iOS, Cordova, ReactNative). Each segment is a list of consecutive route points that are in the same floor. Non-consecutive points within the same floor will be split in different segments. This way, we can just draw one polyline per segment (within the desired floor) and avoid any further trouble.
For example, let’s say a route has 8 data points (P1, P2, P3, P4, P5, P6, P7, P8), being:
- P1, P2 and P3 in Floor 0.
- P4 and P5 in Floor 1.
- P6, P7 and P8 in Floor 0.
Then, we would have 3 segments: 1) segment 1 (P1, P2, P3), 2) segment 2 (P4, P5), segment 3 (P6, P7, P8).
Let’s see some Android code in order to understand this concept:
... SitumSdk.directionsManager().requestDirections(directionsRequest, new Handler<Route>() { @Override public void onSuccess(Route route) { for (RouteSegment segment: route.getSegments()){ Log.i(TAG, "Segment"); Log.i(TAG, "Floor Id: " + segment.getFloorIdentifier()); for (Point point: segment.getPoints()){ Log.i(TAG, "Point: BuildingId "+ point.getFloorIdentifier() + " FloorId " + point.getFloorIdentifier() + " Latitude "+ point.getCoordinate().getLatitude() + " Longitude " + point.getCoordinate().getLongitude()); } Log.i(TAG, "----"); } } ... }
After execution, you will see the following log:
I/MainActivity: Segment Floor Id: 29685 Point: BuildingId 10194 FloorId 29685 Latitude 43.3434333899059 Longitude -8.42761197887612 Point: BuildingId 10194 FloorId 29685 Latitude 43.34352806065825 Longitude -8.427665920512453 Point: BuildingId 10194 FloorId 29685 Latitude 43.343667245037864 Longitude -8.427207329649177 Point: BuildingId 10194 FloorId 29685 Latitude 43.343702404166145 Longitude -8.427225934618166 ---- Segment Floor Id: 29686 Point: BuildingId 10194 FloorId 29686 Latitude 43.34372707052083 Longitude -8.427236219233354 I/MainActivity: Point: BuildingId 10194 FloorId 29686 Latitude 43.343686078274054 Longitude -8.427222991801575 Point: BuildingId 10194 FloorId 29686 Latitude 43.34371318522387 Longitude -8.427139721270171 Point: BuildingId 10194 FloorId 29686 Latitude 43.34377374892849 Longitude -8.4271796783137 ...
Note that each segment indicates its floor identifier, and then contains the list of consecutive points within that list.
Configuring the type of route (accessibility) #
Situm SDK features 3 types of routes:
- Shortest route (default).
- Accessible route (avoids paths that are not suitable for wheelchairs, such as stairs).
- Routes where all floor changes are non-accessible (e.g. avoid routes that use elevators).
Each type of route is explained in detail here (with graphical examples). You may configure the type of route in Situm SDK using the DirectionsRequest accesibilityMode helper (Android, iOS, Cordova, React Native) and data type (Android, iOS, Cordova, React Native). The following Android example shows you how to configure the accessibility mode and log it when the route is computed:
DirectionsRequest directionsRequest = new DirectionsRequest.Builder().from(pointA, null).to(pointB). // We want to compute an accessible route accessibilityMode(DirectionsRequest.AccessibilityMode.ONLY_ACCESSIBLE). build(); SitumSdk.directionsManager(). requestDirections(directionsRequest, new Handler<Route>() { @Override public void onSuccess(Route route) { // The accessibility mode of the computed route will be ONLY_ACCESSIBLE Log.i(TAG, "Accessibility mode " + route.getAccessibilityMode()); } ... });
Retrieving indications #
Aside from the points & segments of the route (which you can display on top of a floorplan), Situm SDK also computes the navigation indications in textual format. You may retrieve the whole set of indications using the Route getIndications helper (Android, iOS, Cordova, React Native). This helper provides a list of Indication data structures (Android, iOS, Cordova, React Native) with several properties, the most important being:
- Type or action (Android, iOS, Cordova, React Native): indicates the type of indication, “go ahead”, “turn” and “change floor” being the most prominent.
- Distance (Android, iOS, Cordova, React Native): indicates the distance to walk until the next indication.
- Orientation (Android, iOS, Cordova, React Native): the angle in radians that the user has to turn.
- Orientation type (Android, iOS, Cordova, React Native): whether the user should turn right or left.
For instance, if the user is heading towards the advance direction of the route, Situm will generate a “Go ahead for 20 meters” instruction (or any other distance). In another case, Situm will generate a “Turn left”, “Turn around” or “Turn right” indication.
Based on these properties, you can generate human-friendly textual indications for the whole route. If you prefer, in native Android/iOS, Situm SDK also provides a toText helper method (Android, iOS) that generates these texts for you in the smartphone locale (english, spanish, portuguese, japanese and arabic supported at the moment). You may try it out by modifying the previous example as follows:
public class MainActivity extends AppCompatActivity { ... Context myContext = this; ... @Override protected void onCreate(Bundle savedInstanceState) { ... SitumSdk.communicationManager(). fetchBuildingInfo(buildingId, new Handler<BuildingInfo>() { @Override public void onSuccess(BuildingInfo buildingInfo) { ... SitumSdk.directionsManager(). requestDirections(directionsRequest, new Handler<Route>() { @Override public void onSuccess(Route route) { for (Indication indication: route.getIndications()){ // We print the indication internals for inspection Log.i(TAG, indication.toString()); // ... and the human-friendly text generated Log.i(TAG, indication.toText(myContext)); } } @Override public void onFailure(Error error) { Log.e(TAG, "Error" + error); } }); } @Override public void onFailure(Error error) { Log.e(TAG, "Error " + error); } }); } }
Which outputs a log like the following:
Indication{stepIdxOrigin=1, stepIdxDestination=1, indicationType=GO_AHEAD, orientationType=STRAIGHT, orientation=0.0, distance=4.623364020533007, needLevelChange=false, distanceToNextLevel=0, nextLevel=null} Go ahead for 5 metres -- Indication{stepIdxOrigin=2, stepIdxDestination=2, indicationType=TURN, orientationType=RIGHT, orientation=-1.570796326794899, distance=40.46055375754866, needLevelChange=false, distanceToNextLevel=0, nextLevel=null} Turn right and go ahead for 41 metres -- Indication{stepIdxOrigin=3, stepIdxDestination=3, indicationType=TURN, orientationType=LEFT, orientation=1.678919122474429, distance=7.953652054245258, needLevelChange=false, distanceToNextLevel=0, nextLevel=null} Turn left and go ahead for 8 metres -- Indication{stepIdxOrigin=4, stepIdxDestination=4, indicationType=CHANGE_FLOOR, orientationType=RIGHT, orientation=-2.288264812469845, distance=1.0, needLevelChange=true, distanceToNextLevel=1, nextLevel=1} Go up to floor 1 -- Indication{stepIdxOrigin=5, stepIdxDestination=5, indicationType=GO_AHEAD, orientationType=STRAIGHT, orientation=0.0, distance=4.67879909805923, needLevelChange=false, distanceToNextLevel=0, nextLevel=null} Go ahead for 5 metres -- Indication{stepIdxOrigin=6, stepIdxDestination=6, indicationType=TURN, orientationType=LEFT, orientation=1.7590508678750336, distance=7.393135870522071, needLevelChange=false, distanceToNextLevel=0, nextLevel=null} Turn left and go ahead for 8 metres -- Indication{stepIdxOrigin=7, stepIdxDestination=10, indicationType=TURN, orientationType=LEFT, orientation=1.9429090871105288, distance=76.97345631265134, needLevelChange=false, distanceToNextLevel=0, nextLevel=null} Turn left and go ahead for 77 metres -- Indication{stepIdxOrigin=11, stepIdxDestination=11, indicationType=TURN, orientationType=RIGHT, orientation=-1.218588055921534, distance=16.402837864223372, needLevelChange=false, distanceToNextLevel=0, nextLevel=null} Turn right and go ahead for 17 metres -- Indication{stepIdxOrigin=12, stepIdxDestination=17, indicationType=TURN, orientationType=LEFT, orientation=0.8838334129694063, distance=102.90426567863084, needLevelChange=false, distanceToNextLevel=0, nextLevel=null} Turn left and go ahead for 103 metres -- Indication{stepIdxOrigin=18, stepIdxDestination=18, indicationType=TURN, orientationType=RIGHT, orientation=-1.5707963267948981, distance=21.192595211547314, needLevelChange=false, distanceToNextLevel=0, nextLevel=null} Turn right and go ahead for 22 metres
The previous log is very interesting because it shows how the text generation works (so you may replicate it if you want). For example:
- The 1st indication is of type “GO_AHEAD” and the distance is “4.62 meters”, so naturally the indication is “Go ahead for 5 meters”.
- The 2nd indication is of type “TURN” and orientationType “LEFT”. Since distance is “7.95 meters”, we conclude that the user should “Turn right and go ahead for 41 meters” (in a way, this is 2 indications in one).
- …
Custom routes #
Situm allows to create customized routes. Custom routes utilize the paths outlined in Situm Dashboard (link) but factor in modifiers known as tags. These tags influence which segments of the route are included or excluded, allowing for tailored navigation beyond simply calculating the shortest or accessible path. This configuration enables the inclusion or exclusion of specific routes.
To carry out this route customization, it is necessary to:
- Go to Situm Dashboard [link] and add tags to the route paths.
- Once these tags are included, it will be possible to include or exclude certain segments of the route. In your application, you will need to identify which tags you want to include in your route, and which ones you want to exclude. This will be explained in detail in this Section.
For example, let’s assume we have two types of routes, “private” and “public.”
Example: what does it mean to include a tag? #
When configuring the Situm SDK in your app to include specific tags( as explained in Section “Customize the tags to include/exclude by your app”), the routing algorithm will consider paths with no tags and the tags you’ve included.
For example, in the following image, there are three types of axes:
- Dark paths: No tag assigned.
- Green paths: Paths with “private” tags.
- Yellow paths: Paths with “public” tags.
Let’s assume three cases:
- Including only the “public” tag: The route will be Yelow paths + Dark paths.
- Including “public” & “private” tags: The route will consider all paths.
- Including only the “private” tag: The route will use green paths + dark paths“.
As a result, if you include only “private” tags, the route will be:
Example: what does it mean to exclude a tag? #
Using the same example as above (Dark paths, green paths and yellow paths) and if you configuring the Situm SDK in your app to exclude specific tags( as explained in Section “Customize the tags to include/exclude by your app”)
Let’s assume three cases:
- Excluding only the “public” tag: The route will be green paths + Dark paths.
- Excluding “public” & “private” tags: The route will consider only dark paths.
- Excluding only the “private” tag: The route will use Yelow paths+ dark paths“.
If we want to exclude “public” tags in your app, the route will be:
Configuring routes by time #
It is possible to exclude certain paths from a route based on a time range. This functionality relies on the exclusion of paths using tags as previously explained link. To activate it, you need to add tags with the corresponding time range to the paths you wish to exclude at specific hours, using the following format “hh:mm-hh:mm”.
For example, if you want to exclude certain paths between 15:00 and 16:00, you need to add a tag to those paths in the following format: “15:00-16:00. This way, the calculated routes will not include those paths during the specified time range.
Important note: The tags included in the paths to exclude these paths must be in the local time format of the users that will use your application.
Customize the tags to include/exclude by your app #
There are two options if you want to include or exclude tags in your app:
Using the MapView component (Recommended) #
We recommend this option to integrate this functionality. You need to wait until the MapView finish loading and then you can do this:
val excludedTags = listOf("tag1", "tag2") val includedTags = listOf("tag3", "tag4") val options = MapViewDirectionsOptions.Builder() .setExcludedTags(excludedTags) .setIncludedTags(includedTags) .build() //The mapViewController is obtained in the onLoad callback mapViewController.setDirectionsOptions(options)
let includedTags = ["tag1", "tag2"] let excludedTags = ["tag3", "tag4"] let mapViewDirectionsOptions = SITMapViewDirectionsOptions(includedTags: includedTags,excludedTags: excludedTags) mapViewController?.setDirectionsOptions(mapViewDirectionsOptions)
//Call this after the mapview finished loading let directionsOptions: MapViewDirectionsOptions = { excludedTags: ['tag1','tag2'], includedTags: ['tag3','tag4'], } _mapViewRef.setDirectionsOptions(directionsOptions);
Using the SDK #
This is not recommended, but you also can configure your DirectionsRequest with the included tags that you want. Look at this example:
val excludedTags = listOf("tag1", "tag2") val includedTags = listOf("tag3", "tag4") val directionsRequest = DirectionsRequest.Builder() //Add the other neccessary things in the directionsRequest and then add the tags .includedTags(includedTags) .excludedTags(excludedTags) .build() SitumSdk.directionsManager().requestDirections(directionsRequest, object: Handler<Route> { override fun onSuccess(obtained: Route?) { } override fun onFailure(error: Error?) { } })
let directionsRequest = SITDirectionsRequest(location: <SITLocation>, withDestination: <SITPoint>, includedTags: includedTags, excludedTags: excludedTags) SITDirectionsManager.sharedInstance().requestDirections(request)
const includedTags = ['tag1','tag2']; const excludedTags = ['tag3','tag4']; SitumPlugin.requestDirections(_building, from, to, { minimizeFloorChanges, accessibilityMode, bearingFrom, includedTags, excludedTags, })