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.

Ok - then it works the way its supposed to, thanks @rlkoshak

Anyone know if this works for openHAB 3.3? I just get “Position data is not loaded.” I set openHAB to debug and I don’t see any rest/items API hits even trying to pull my location. I do see it pull map21.js and openHAB.js.

Is there a better way to debug this, or is debug in openhab the only way?

It’s been quite some time since I’ve used this. Since COVID the family is basically home all the time so I never rewrote this for OH 3 as it was pointless for us.

That error means either the raw Location Items are not changing or the rules are not running to populate the PersonXLocationHistory Items.

Pay attention to the prerequisites. You have to set up GPSTracker as well as a GPS tracking app on your phone. It should work with the iCloud binding too if Apple is your thing. All the rules and map page care about are the Location Items changing, regardless of how. But the PersonXLocation Items have to be changing. It’s the changes that drive the rules.

The position Channels from the GPSTracker Things must be updating with your location. Setting that up is all a prerequisite and outside the scope of this tutorial. Nothing here is going to work until you have that part working. If you see your Location Items updating in events.log, we can go from there.

Additionally, the rules as written use the Jython add-on working in OH 3 and have the helper library installed and configured. But with all that the rule should work as written. They are pretty simple rules that mainly do some string manipulation on a String Item.

I’ve not tried to use this embedded map on MainUI. I see no reason for it not to work but I’ve never tried it.

It probably would be better to rework this whole tutorial to use the map screen built into OH anyway and the rules could be rewritten as a rule template. But I have bigger fish to fry at the moment. If someone decides to take that on, I’ll happily update the OP with an updated version.

Were I to write this for OH 3 from scratch I’d use a collection of Location Items with Expire configured to time them out after an hour or so. These would all be put on the MainUI Map page. When the Item linked to the. GPSTracker changes there’d be a rule to update all the history Items. The Items themselves could be created by the rule even.

From a performance perspective this will work a whole lot better than the above. I found that if I took even a short road trip it would take the map a long time to render all the little points since it keeps them for a whole day before refreshing. So instead it’d keep just the most recent hour’s worth of locations or, if I’m moving a lot, the most recent ten (or what ever makes sense) locations.

It also gets rid of the .js files and the custom web page that needs to be loaded into a webview. It’s all straight forward Items and Groups.