Google Map

As I reset the history after one day the string works for me; but of course you can also modify the example to use persistence and a location item. The openHAB.js already has some utility functions to get item history (note, you´ll need jquery csv from GitHub - evanplaice/jquery-csv: A jQuery CSV parser plugin. Battle Tested | Optimized | 100% IETF RFC 4180 Complete ).

It updates on every location change. I use a string as I did not want to deal with persistence in the javascript yet (but did some preparations) & for one day a string works fine. So can´t help you with the persistence approach yet - but I´m sure it can be done w/o to many modifications. For charts I read history items via javascript, if desired I can post some example that might help you to start. As usual, those are not expert/reference snippets - just the way I could get it to work.

Managed to get it working.
For reference for anyone else wanting to do this; use an editor of choice.

Create all files necessary according to above and find/replace all Patrik (or Karin) with your choice of name in all applicable files.

I used MAPS EMBED API on google (https://developers.google.com/maps/documentation/embed/).
Change it in the files applicable (rules, html).

In owntracks android application under preferences and connection, you edit the identification to your choice.
Example:
Username: Nicklas
Device ID: Android
Tracker ID: NS (this is arbitrary but must be 2 digits or letters)
Now edit in items file and replace according to what you chose:
String ItemMQTT { mqtt="<[broker:owntracks/Nicklas/Android:state:default]" }

Important, in rule MQTT you must edit this line to whatever the tracker ID you gave in owntracks application configuration: if (_id == “NS”) {
In Patriks example, he has PG.

In the rule initializeSystem, edit with your home coordinates (and googlemapAPI).

This was pretty easy to set up when you know what to do, but I had to troubleshoot the “PG” to make it work.
Thanks @patrik_gfeller for the code!

If one looks more closely, they will see that you get some information from the phone as well, like battery.
Add to sitemap:
Text item=ItemNicklasNatelBattery
And for instance a switch that shows if you are home or not in sitemap:
Switch item=ItemNicklasAtHome

@patrik_gfeller, I have some problem displaying it in HABDroid. It’s just a white box.
In chrome it works but does not refresh.
Also the zoom seems off when I open it and I manually have to center the map.
Any pointers?

1 Like

Unfortunately I do not know why it does not work in HABDroid - as I use web UI only. The center of the map should be set in function “initializeMap”:

        function initializeMap() {
            // Get home location from openHAB ...
            var location = GetOpenHABItemState("ItemLocationHome");
            // Convert to google data type ...
            var locationHome = new google.maps.LatLng(
                parseFloat(location.split(',')[0]),
                parseFloat(location.split(',')[1]));

            // create map (centered @ home) ...
            map = new google.maps.Map(document.getElementById('map'), {
                center: locationHome,
                mapTypeId: google.maps.MapTypeId.TERRAIN,
                zoom: 10
            });

For the auto-zoom the bounds are important; check that they are set and updated when you add markers (as in the example). The last line of the init map then should set the zoom:

map.fitBounds(bounds);

At the moment the map does not yet auto refresh; you can consider to add a meta to the html to automatically refresh:

<meta http-equiv=”refresh” content=”60" />

with kind regards,
Patrik

1 Like

Hi,
I am new in openhab and at first thank you for the code, but I have some questions:

  1. I got the map on the sitemap, but the zoom is to much (no road or something else next to my home icon).
    How could I adjust this?

  2. I will also show the battery, accuracy, the location string (lon, lat) in a group.
    But there is nothing, no data.

Could someone help me?


UPDATE: I found my mistake (the configuration in the OwnTrack app - mobile phone - was wrong)
Everything work now how it should

1 Like

Hi all,

I did a major refactoring of the map display to make it more flexible and easier to use. It uses now ajax/xhr to update the positions (without the need to reload the page). Also some decent animation was thrown in.

2019-02-25%2009_11_31-Window

To use please download the “map.zip.txt”, rename it & place the content in “conf/html/”.

map.zip.txt (12.8 KB)

To configure add your Google API key in “index.html”. The markers can be configured in “./lib/map21.js”:

2019-02-25%2009_19_01-Window

You can configure as many elements as you need.

Hope this is of use :slight_smile:.

with kind regards,
Patrik Gfeller

2 Likes

Hi @patrik_gfeller,

Thanks for putting the time into this. It is much appreciated. Are you paying google for the api access? I ended up shelving the feature in my sitemap due to google starting to charge for the api calls.

Hi Paul,

I think there is still a free quota; it lead I do not pay for my requests to the AP.

with kind regards,
Patrik

@patrik_gfeller Thanks for releasing this for the community to use!
Is it possible for you to include some new examples of things/sitemap/rules that use your new code?
It looks like it may have changed?

I know this is getting on a year old but I’m trying to make it work, So far I’ve failed but I can’t see what I’m doing wrong. I’m using GPSTracker which makes things significantly simpler and based on watching events.log the History Item is being populated correctly. Here’s what I’ve done…

NOTE: I’m not using the gecoding API so I didn’t reproduce that Rule.

Preconditions

  • GPSTracker installed and configured correctly.
  • OwnTracks installed on phones and reporting to OH.
  • Things created for each of the reporting phones and the Channels linked to Items.

Get 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. I already have a billing account. I chose the Maps JS API. Copy the key, we will need it later.

@sintei, it would be helpful if you posted exactly what you needed to change to get the Embedded API URL to work. I tried and failed.

Items

I created a few new Items.

Location vHome "Home Location"

String vMaps_API "Google Maps API Key"

Group:String LocationHistories

Group:Location Locations

// Rich
Location vRich_Location "Rich's Current Lat/Lon [%s]"
    <motion> (gChart, Locations)
    { channel="gpstracker:tracker:RK:lastLocation" }

Number vRich_Phone_Battery "Rich's Phone Battery [%d %%]"
    <battery>
    { channel="gpstracker:tracker:RK:batteryLevel" }

DateTime vRich_Last_GPS "Rich's Last Location Report [%1$tm/%1$td %1$tH:%1$tM]"
    <time>
    { channel="gpstracker:tracker:RK:lastReport" }

Number:Length vRich_GPS_Accuracy "Rich's Location Accuracy [%d ft]"
    <motion>
    { channel="gpstracker:tracker:RK:gpsAccuracy" }

String vRich_Region "Rich's Location [%s]"
    <motion>

Number:Length vRich_Distance "Rich's Distance from Home [%.1f mi]"
    <motion>
    { channel="gpstracker:tracker:RK:distanceSystem" }

String vRich_Location_History
    (LocationHistories)

// Jenn
Location vJenn_Location "Jenn's Current Lat/Lon [%s]"
    <motion> (gChart, Locations)
    { channel="gpstracker:tracker:JC:lastLocation" }

Number vJenn_Phone_Battery "Jenn's Phone Battery [%d %%]"
    <battery>
    { channel="gpstracker:tracker:JC:batteryLevel" }

DateTime vJenn_Last_GPS "Jenn's Last Location Report [%1$tm/%1$td %1$tH:%1$tM]"
    <time>
    { channel="gpstracker:tracker:JC:lastReport" }

Number:Length vJenn_GPS_Accuracy "Jenn's Location Accuracy [%d ft]"
    <motion>
    { channel="gpstracker:tracker:JC:gpsAccuracy" }

String vJenn_Region "Jenn's Location [%s]"
    <motion>

Number:Length vJenn_Distance "Jenn's Distance from Home [%.1f mi]"
    <motion>
    { channel="gpstracker:tracker:JC:distanceSystem" }

String vJenn_Location_History
    (LocationHistories)

Rules

I use Python but these are simple enough they should be understood by all. Because the GPSTracker handles parsing the messages from OwnTracks and I’m not doing geocoding yet so all I need to do is update the history Items.

@rule("Clear Histories",
      description="Clears location histories",
      tags=["location"])
@when("Time cron 0 0 0 ? * * *")
def clear_hist(event):
    for loc in ir.getItems("LocationHistories").members:
        events.postUpdate(loc.name, "")

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

Watching events.log, my History Item is updating as expected:

<lat>,<lon>;<lat>,<lon>;

No spaces. NOTE that I had to initialize the History Item to “”. I need to update the code to handle that case, but I mention it here. Without initializing the Item it ends up starting with NULL.

Webview

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

index.html

  • Change the lang attribute at the top to “en”.

  • Change the error message “Positionsdaten sind nicht geladen.” to “Position data is not loaded.”

  • Inserted my API Key where indicated at the bottom of the file and change the end of the URL to initMap instead of initialize. The example URL on Google’s site uses initMap. It doesn’t work either way sadly. :frowning:

<!DOCTYPE html>

<html lang="en">

<head>
    <meta charset="utf-8">
    <!-- Force latest IE rendering engine & Chrome Frame -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="description" content="Displays the current user locations and the location history data in Google-Maps.">
    <meta name="author" content="Patrik Gfeller">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">

    <title>Positionsinformationen</title>

    <link href="./css/style.css" type="text/css" rel="stylesheet">

    <script src="./lib/openHAB.js"></script>
    <script src="./lib/map20.js"></script>
</head>

<body>
    <main>
        <div id="map" class="map">
            <span id="errorMessage" class="errorMessage">Position data is not loaded.</span>
        </div>
    </main>

    <!-- Google-Maps API must be loaded after DOM is initialized completely. -->
    <script
        src="https://maps.googleapis.com/maps/api/js?key=<MY API KEY>&callback=initMap"
        async defer></script>
</body>

</html>

lib/map21.js

Populated openHABLocations with my Items.

I check that the Items are populated properly but whenever I load the page I get the “Position data is not loaded.” error. I’ve double checked for typos everywhere I can think of to no avail. Everything

    var openHABLocations = {
        "Home": {
            locationItem: "vHome",
            locationIcon: "./img/home.png",
            locationType: locationTypes.home,
            animation: null,
            historyItem: undefined,
            historyIcon: undefined,
            zIndex: 10
        },
        "Rich": {
            locationItem: "vRich_Location",
            locationIcon: "./img/patrik.png",
            locationType: locationTypes.user,
            animation: google.maps.Animation.DROP,
            historyItem: "vRich_Location_History",
            historyIcon: "./img/historyPatrik.png",
            zIndex: 9
        },
        "Jenn": {
            locationItem: "vJenn_Location",
            locationIcon: "./img/karin.png",
            locationType: locationTypes.user,
            animation: google.maps.Animation.DROP,
            historyItem: "vJenn_Location_History",
            historyIcon: "./img/historyKarin.png",
            zIndex: 8
        }
    };

Any assistance or information telling me this no longer works would be appreciated.

It sounds all good to me… Can you try to open the page in a browser to check the console and network traffic (developer tools)

If I remember correctly initialize is the name of the callback in the code. You should not change it, or you need to update the function name as well.

(from phone)

Thanks! To simplify things I eliminated “Jenn” so it’s only configured with “Rich” from the code above.

I should have thought to do that myself. Looks like there are two problems. The first is the .js file is map21.js but the index.html file is trying to load map20.js.

GET https://openhab.koshak.us/static/map/lib/map20.js net::ERR_ABORTED 404 (Not Found)

That’s fixed easily enough.

The second is complaining about the initMap.

index.html:1 Uncaught (in promise) xd {message: "initMap is not a function", name: "InvalidValueError", stack: "Error↵    at new xd (https://maps.googleapis.com/m…iptnI2N8Lod4uKjRJxkk6CFE&callback=initMap:146:124"}

I tried using map.initMap as well as your original map.initialize and get the same error. I got the callback=initMap from Google’s website though now that I look at it, the callback is a function we define. But I don’t know JS well enough to understand where map is defined. I see MapObject in map21.js. But it’s clear to me now that initMap is bogus. I just need to figure out how to properly reference the right entity defined in map21.js.

I have something to play with now but any leads would be helpful. Thanks!

EDIT: I got rid of the map. and now it works like a champ.

I’ll take the above and post a new tutorial to show people how to set this up with the GPSTracker binding. Sometimes all I need is a little hint like “look at the logs.” :smiley:

1 Like

sorry for not responding earlier, but it seems you got it to work!
Just a note, but make sure you set amount of calls in google cloud or you will get a nice surprice bill :wink:

I’d still like to know what you did to get the Embedded Map API to work, in particular the code put into the HTML and JavaScript. I tried a bunch of stuff that seemed to make sense and couldn’t get that API to work. As you hint at, the Embedded API doesn’t cost while the JavaScript one does. Though so far I’ve used up $0.08 so it’s not going to break the bank.

I’m unsure about this, but if I look I only have one html (map.html) in a folder called “google-maps” and I have not edited anything in that one.
However, in my rule file I have:

/*rule InitializeSystem
    when
        System started
    then
        ItemLocationHome.postUpdate(new PointType("longitude,lattitude"))
        ItemGoogleMapAPIKey.postUpdate("my API key")
    end

In google I have enabled several different API which my key can access.
Can’t remember that I used .JS at all :frowning:

If you are using the embedded API as you indicated in the post above, at a minimum you would need to change the index.html to use the embedded end point instead of the JS endpoint.

    <!-- Google-Maps API must be loaded after DOM is initialized completely. -->
    <script
        src="https://maps.googleapis.com/maps/api/js?key=<key>&callback=initialize"
        async defer></script>

That’s the line that uses the JS end point. At a minimum you would need a URL that starts with "https://www.google.com/maps/embed/v1/... to use the embedded API. But as far as I can tell the embedded one doesn’t support the callback.

Hmm, in my html file the closest I have is this:

		// Create script element with google maps API key
		var script = document.getElementById("googleMapsUrl");
		script.src = "https://maps.googleapis.com/maps/api/js?key=" + gm_apikey + "&callback=initialize";

Maybe this will help some?
Items file:

//GoogleMap
String		ItemMQTT 																					{ mqtt="<[broker1:owntracks/sintei/Gold:state:default]" }
String		ItemNicklasLocationHistory
String		NicklasLocationString 	"Nicklas [%s]" 										<place>

Location	ItemNicklasLocation 	"Nicklas" 											<location>

Number		ItemNicklasLocationAccuracy
Number		ItemNicklasNatelBattery	"Natel Nicklas [%d %%]"								<battery>		(GroupPersistrrd4jMinute,GroupBattery)

DateTime	ItemNicklasLastPositionUpdate	"Nicklas - last Update [%1$tH:%1$tM, %1$td.%1$tm.%1$ty]" <location>

Switch		ItemNicklasAtHome		"Nicklas" 											<home>
Location	ItemLocationHome
String		ItemGoogleMapAPIKey

String		GoogleMapsDistanceNicklas	"Distance Nicklas [%s]"												(gGoogleMaps)			{ http="<[https://maps.googleapis.com/maps/api/distancematrix/json?origins=LATTITUDE,LONGITUDE&destinations=LATTITUDE,LONGITUDE&language=SE&departure_time=now&traffic_model=best_guess&mode=driving&key=YOUR API KEY!!:300000:JSONPATH($.rows[0].elements[0].distance.text)]" }
String		GoogleMapsDurationNicklas	"Duration Nicklas [%s]"												(gGoogleMaps)			{ http="<[https://maps.googleapis.com/maps/api/distancematrix/json?origins=LATTITUDE,LONGITUDE&destinations=LATTITUDE,LONGITUDE&language=SE&departure_time=now&traffic_model=best_guess&mode=driving&key=YOUR API KEY!!:300000:JSONPATH($.rows[0].elements[0].duration.text)]" }
String		GoogleMapsDurationTrafficNicklas	"Duration [%s]"												(gGoogleMaps)			{ http="<[https://maps.googleapis.com/maps/api/distancematrix/json?origins=LATITUDE,LONGITUDE &destinations=LATTITUDE,LONGITUDE&language=SE&departure_time=now&traffic_model=best_guess&mode=driving&key=YOUR API KEY!!:300000:JSONPATH($.rows[0].elements[0].duration_in_traffic.text)]" }

OK, then you are not using the embedded google maps api then. That’s the JS endpoint.

sad to say, this is over my head :blush: