GPSTracker + Google Map

By default the Mapview element in OH only supports showing one location at a time. If you are tracking multiple people you need more than one Mapview. This tutorial is based on Google Map and most of the credit goes to @patrik_gfeller . All I’ve done is update it to use the GPSTracker binding instead of MQTT and Scripted Automation using Python for the Rules. I’ve also not implemented the geocoding rules as Google now charges $5 per 1k requests a month. It would not be difficult to implement if you want to reproduce that feature.

This tutorial will result in Google Maps in a custom webview that shows the current location and all the past locations for the past 24 hours for all your configured trackers.

Preconditions

  • You have GPSTracker installed and configured correctly. See the binding read me for details.
  • You have OwnTracks or GPSLogger installed on your phones and reporting to OH.
  • You have Things created for each of your reporting phones and the Channels linked to Items.

Get your Google Maps API Key

Navigate to https://developers.google.com/maps/documentation/embed/start and select “Get an API key” on the left and follow the instructions. You will need to create a billing account with Google but the API.

Be sure to select the Maps JS API. Copy the API key, you will need it later.

Items

The map will show your most recently reported locations, all the past reported locations, and static locations. But all the information it displays comes from Items. The following Items are needed for each user:

  • Location: last reported location
  • Location_History: a String listing each lat,lon reported since midnight of the format <lat>,<lon>;<lat>,<lon>;
  • An Item for all static locations (e.g. home)
Group:String LocationHistories // All the Location_History Items 
Group:Location Locations       // All the Location Items
Location Home "Home Location"  // Lat,Lon for your home

Location Person1Location (Locations) { channel="gpstracker:tracker:P1:lastLocation" } 
String Person1LocationHistory (LocationHistories)

Location Person2Location (Locations) { channel="gpstracker:tracker:P2:lastLocation" }
String Person2LocationHistory (LocationHistories)

Use appropriate Item names and the correct Thing IDs in the Channel Links.

Rules

Because the GPSTracker binding does so much for you, the Rules are very simple. Please read the comments for details.

@rule("Initialize Maps Items",
      description="Initialize Items for Google Maps Webview",
      tags=["location"])
@when("System started")
def loc_init(event):
    """
    Initialize the static location Items. If you use restoreOnStartup on these Items,
    this Rule can be removed after the first time it runs. Alternatively you can set
    the state of the static Items using the REST API instead.
    """
    events.postUpdate("Home", "<HOME LAT>,<HOME LON>")

@rule("Clear Histories",
      description="Clears location histories at midnight",
      tags=["location"])
@when("Time cron 0 0 0 ? * * *")
def clear_hist(event):
    """
    Clears the history Items at midnight, resetting them to the current location.
    The LocationHistories Items must have at least one value so if the current
    state is NULL or UNDEF than initialize the history with the Home location.
    """
    for loc in ir.getItem("LocationHistories").members:
        clear_hist.log.info("Resetting {}:{}".format(loc.name, items[loc.name]))
        locItem = loc.name[:-8]
        init = items[locItem]
        if isinstance(init, UnDefType):
            init = items["vHome"]
            events.postUpdate(locItem, str(items["Home"]))
        events.postUpdate(loc.name, "{}".format(init))

@rule("Location Changed",
      description="Updates location history",
      tags=["location"])
@when("Member of Locations changed")
def loc_changed(event):
    """
    Updates the location history Item with the newly reported location.
    """
    if not isinstance(event.itemState, UnDefType):
        hist_item = "{}_History".format(event.itemName)
        events.postUpdate(hist_item, "{}{};".format(items[hist_item], event.itemState))

See the original posting if you want to implement geocoding to get the current address of the user which would be another simple Rule to request the address using an sendHttpGetRequest call and updating an Item with the result.

Webview

Download, change the name and unzip map.zip.txt from Google Map. Unzip this folder to $OH_CONF/html. Make the following edits:

map/index.html

  • Change the lang attribute to the top if “de” isn’t appropriate for you.

  • Change the map20.js to map21.js or alternatively change the name of the file in map/lib.

  • Change the error message “Positionsdaten sind nicht geladen.” to what’s appropriate in your language. English is “Position data is not loaded.”

  • Insert your API Key where indicated at the bottom of the file and change the end of the URL to initialize instead of map.initialize.

lib/map21.js

Populate the openHABLocations with your Items. The “Home” section shows how to display a static location. The “Patrik” and “Karin” sections need to be updated with the names and Items defined above.

If you don’t want to show the history for one or more people, you can turn that off by setting the “historyItem” and “historyIcon” to “undefined”. In that case you can eliminate the Rules above as well.

images

You will find the example images in map/img. The default sets are pretty generic and usable but if you have more than two people to track or more static locations to define you will want to add and customize. One idea includes using the person’s face for the location icon.

Experience

The resulting webview is quite nice and easy to use on the web page. However, it’s all but impossible to scroll around the map when viewing the webview in the Android app when showing a sitemap. I’ve not yet experimented with HABPanel views, perhaps they work better.

Good luck!

5 Likes

Thanks Rich, another bookmark, :grin: will have to give this a try once I get the Scripted Automation stuff installed on my system.

You can go to the original posting and see Patrik’s original Rules DSL version.

The rules in python do not run on OH3.2M2
Can someone share the ECMAScript equivalent of below Python rules?
Some help would be much appreciated.

@rule("Clear Histories",
      description="Clears location histories at midnight",
      tags=["location"])
@when("Time cron 0 0 0 ? * * *")
def clear_hist(event):
    """
    Clears the history Items at midnight, resetting them to the current location.
    The LocationHistories Items must have at least one value so if the current
    state is NULL or UNDEF than initialize the history with the Home location.
    """
    for loc in ir.getItem("LocationHistories").members:
        clear_hist.log.info("Resetting {}:{}".format(loc.name, items[loc.name]))
        locItem = loc.name[:-8]
        init = items[locItem]
        if isinstance(init, UnDefType):
            init = items["vHome"]
            events.postUpdate(locItem, str(items["Home"]))
        events.postUpdate(loc.name, "{}".format(init))

@rule("Location Changed",
      description="Updates location history",
      tags=["location"])
@when("Member of Locations changed")
def loc_changed(event):
    """
    Updates the location history Item with the newly reported location.
    """
    if not isinstance(event.itemState, UnDefType):
        hist_item = "{}_History".format(event.itemName)
        events.postUpdate(hist_item, "{}{};".format(items[hist_item], event.itemState))

@rlkoshak

Hi
I have tried this and it works perfect :slight_smile:

I have one question - the markers, are they suppose to be updated without refreshing?

Best
Nanna

No, the way this works they only get updated when the map first loads. There must be a refresh to get new markers.