Geographic information system

logo

overview

Blockly addons to handle GEO positions and calculate the distance between two GEO positions.

Blocks

Distance between %1 and %2

Calculate the distance between the two arguments. Both arguments have to be internal Position objects.

Format %1 as %2

Format the given position in the given format (dummy).

Convert %1 meter to %2

Convert the parameter (Number) from meter to the given non-meter Unit.

Convert %1 in unit %2 to meter

Convert the parameter (Number) in the selected non-meter unit to meter.

Position at latitude %1 and longitude %2 (Direct input)

Create a position object at the given latitude and longitude position. The arguments are direct numbers and there are validators.

Position at latitude %1 and longitude %2 (From Blocks)

Create a position object at the given latitude and longitude position. The arguments are other Number Blocks.

Changelog

Version 0.2

  • Fix colors and validation for input arguments

Version 0.1

Resources

https://raw.githubusercontent.com/lochmueller/openhab-block-libraries/e2b6b70b85d4501864f55e8ade1e42073f32a561/gis/BlockLibrary.yaml

6 Likes

Oooooh man.
If only the android app returned the gps position of the smartphone… you could then have the distance to the openHAB instance.
And run/turn off stuff as you get nearer or farther from home.

This could be cool!

1 Like

Nice idea, Tim, thanks for the contribution. This is exactly what the block libraries are for. Looks already pretty good

Here is my feedback from reviewing your code and documentation:

  • blocks returning a number (output: Number) should use the same color 230 like in the core block library, Only blocks of type Geoposition should have the 300

  • in general each type should have a (slightly) different color

  • you should make sure that only valid blocks can be applied. Here is an example which is probably not intended

  • there is a small typo “formated” instead of formatted :angel:

regarding

Format the given position in the given format (dummy).

What do you mean with “dummy”?

The arguments are direct numbers and there are validators

Can you explain the documentation what the validators do? I think you are referring to -180 → 180 degrees

I would be also >nice< if you provided some details in the documentation how to use that in openHAB (e.g. via the GPS Tracker Binding?)

Hope that helps,
Stefan

Hey @stefan.hoehn
thanks for your feedback. I changed the colors, fix the typo and release a version.
I will check the GPS Tracker Binding and also the native Location Type in the future.
Regards,
Tim

1 Like

Hey Tim

Tried the block out this evening. Seems to work.

constructive feedback ( i will try):

  • maybe the tooltip for the “distance between block” could state that the distance is in meters (if that is the case?)
  • I don’t know how to do it, but maybe the splitting of a location type into the two (or three if there is an altitude) parts could be a separate function block?
  • Could one maybe make a new position input that would be able to take a list with the two keys lat and long (or even better just two or more keys, in case there is also an altitude in the location)

For those who might also want to try it, I had one bear of a time getting the location type item that i started with into two numbers (one for latitude and one for longitude), but finally i found out that inserting an “inline script” with the code

coord_pair_string = String(location_item_state)

Converts the state to a string, and from there you can split the (now) text string either by selecting sub strings, or by finding the index of the comma and splitting the string by index.
Probably I could also with some trial and error have written an inline piece of code that did the splitting, and outputting to the needed variables, but that was one bridge too far for me at this point in time.

1 Like

can you show me that part of the blocks over there, maybe we can improve something on the core blocks to avoid the script in the future on location time items.

I’m pleased to see that this is being developed. I have a couple of questions or comments based on my experience with the code I have used for this:


val currentLocation = (newState as PointType)
val lastLocation = (previousState as PointType)
val homeLocation = new PointType(new DecimalType(xx.093643), new DecimalType(-XX.337313), new DecimalType(0))
//val G_WalletLocation = GeorgesWalletAccessory_Location.state as PointType
val distanceFromHome = currentLocation.distanceFrom(homeLocation).toString + " m"
//val walletDistanceFromPhone = G_WalletLocation.distanceFrom(currentLocation).toString + " m"
GeorgeDistanceFromHome.postUpdate(distanceFromHome) 
//GeorgeWalletDistanceFromPhone.postUpdate(walletDistanceFromPhone)  

I have used the Location channel in the iCloud binding for the phone location, but I found I had to cast it as a PointType to use the distanceFrom method. The linked Item shows as having a Location type. Is that the same as the Position type referenced here?

Is the result of the distance calculation block a QuantityType that includes a Unit of Measure (UoM)?

(I’m not a developer and I’m probably not asking the questions correctly, but I hope you understand what I’m trying to ask.)

I’m looking forward to replacing my code with this in the future.

Can someone confirm, that the internal “Location” type is just a “float,float,float” (lat, lon, ati) representation?! That I would drop my internal “GeoPosition” Type and switch to the “Location”/" PointType"

maybe the tooltip for the “distance between block” could state that the distance is in meters (if that is the case?) → Feel free to send a PR: openhab-block-libraries/gis at main · lochmueller/openhab-block-libraries · GitHub

The linked Item shows as having a Location type. Is that the same as the Position type referenced here? I don’t know :-/

My blockly integration result is meters, but do not contain UoM.

Regards,
Tim

1 Like

In a simplified sense:

If you look at LocationItem it shows that it is a list of PointType which itself contains several DecimalType(s) which in essence are BigDecimals.

The PointType shows you via the constructors that it may have 2 or three fields which basically means you could omit the altitude which would result into an altitude of 0.

Note that there is nothing like a “PositionType” → see here for a list of types

Does that help?

Thanks again for this library. It has enabled me to greatly simplify my presence change action rules.

I did encounter one unexpected result, that I was able to work around. In addition to getting the distance from home that I use to set a presence switch, I want to get the distance between my watch and my phone. I usually don’t take my phone on a walk or bike ride, but the phone seems to respond more quickly to the network binding presence detection and I am using the GPS Tracker binding with OwnTracks (which I find is more real time for arrival/departure than the every 5 minute iCloud blinding location updates). But that means I need to know whether to consider the phone’s input, and the requires knowing if the phone is with the watch. (Sorry for the long explanation of the use case).

When I first tried using the Distance between block using the newState for the Phone’s location and the watch location item state for the watch, the block would not let me attach the “get state of item” block with the watch location item. I then tried using the watch location item instead of its state. The block let me attach that, but the result was

2022-09-03 08:52:46.544 [WARN ] [rnal.defaultscope.ScriptBusEventImpl] - State 'NaN' cannot be parsed for item 'GeorgeWatchDistanceFromiPhoneiCloud'.

I then tried creating a variable to which I assigned the watch location item. The block accepted that variable, and the code now works as expected.

Here is the current code that works:

configuration: {}
triggers:
  - id: "1"
    configuration:
      itemName: GeorgesiPhoneiPhone13Pro_Location
    type: core.ItemStateChangeTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      blockSource: '<xml
        xmlns="https://developers.google.com/blockly/xml"><variables><variable
        id="vwjL40[(bStpY%37YS`A">GWatchLocation</variable></variables><block
        type="variables_set" id="hvISg+!AhH).zM}?k.V8" x="62" y="184"><field
        name="VAR" id="vwjL40[(bStpY%37YS`A">GWatchLocation</field><value
        name="VALUE"><block type="oh_getitem_state"
        id="]!^!3`C5_1Qn8Cvs}w_y"><value name="itemName"><shadow type="oh_item"
        id="``Arfe,D7;]qDG}I%dDd"><field
        name="itemName">GeorgesAppleWatchAppleWatchSeries7GPSCellular_Location</field></shadow></value></block></value><next><block
        type="oh_log" id="fn:IL[;|E4MaQBE$+hQK"><field
        name="severity">info</field><value name="message"><shadow type="text"
        id="~8P-BoM3~qG$Vw*y(op-"><field name="TEXT">abc</field></shadow><block
        type="text_join" id="];D!jCb!h-u*$itqP,L`"><mutation
        items="7"></mutation><value name="ADD0"><block type="oh_context_info"
        id="V,a4+SG{J]!NQzp5kH[0"><field
        name="contextInfo">itemName</field></block></value><value
        name="ADD1"><block type="oh_context_info"
        id="Bcm,t!^|mP.{+t2uYPiD"><field
        name="contextInfo">itemState</field></block></value><value
        name="ADD2"><block type="oh_text_crlf"
        id="$QYVV;z1[^^**K4nTi/|"></block></value><value name="ADD3"><block
        type="oh_getitem_attribute" id="!Pc/ML[!xE}Idzf{4J32"><mutation
        attributeName="Name"></mutation><field
        name="attributeName">Name</field><value name="item"><shadow
        type="oh_getitem" id="ve~F[Fvu%o5BdFfHtY.I"><value
        name="itemName"><shadow type="oh_item" id="PaxQj1.hWWsxmqXO90E,"><field
        name="itemName">GeorgesiPhoneiPhone13Pro_Location</field></shadow></value></shadow></value></block></value><value
        name="ADD4"><block type="oh_getitem_state"
        id="7RGzFa:w0YFALVZgu4NJ"><value name="itemName"><shadow type="oh_item"
        id="vf}KCdeqp}MYgI?F)w/R"><field
        name="itemName">GeorgesiPhoneiPhone13Pro_Location</field></shadow></value></block></value><value
        name="ADD5"><block type="text" id="SA|DePp7#nWo6}(#c],u"><field
        name="TEXT"> Variable: </field></block></value><value name="ADD6"><block
        type="variables_get" id="-?n=|H1s#Z,[}!2@p]?S"><field name="VAR"
        id="vwjL40[(bStpY%37YS`A">GWatchLocation</field></block></value></block></value><next><block
        type="controls_if" id="39L-skYht3H:%kR6oU*i"><mutation
        else="1"></mutation><value name="IF0"><block type="logic_compare"
        id="50{sPiD|00wao]|CC+.~"><field name="OP">NEQ</field><value
        name="A"><block type="oh_context_info" id="?+]tLGF-QBwAgyOiJpj0"><field
        name="contextInfo">itemState</field></block></value><value
        name="B"><block type="text" id="cb34kc@sV^;1Gxs.R5Gu"><field
        name="TEXT">UNDEF</field></block></value></block></value><statement
        name="DO0"><block type="oh_event" id="C`eFJ7AKN$E9}GyQyeiI"><field
        name="eventType">postUpdate</field><value name="value"><shadow
        type="text" id=",HT]dv%XT]h$*YXkC[5`"><field
        name="TEXT">value</field></shadow><block
        type="lochmueller:blockly:gis_gis_distance"
        id="DRx-XEJ+r=^|SEQCWeI4"><value name="POSITION1"><block
        type="oh_context_info" id="=VT`dwzRqN4,{S@SFehH"><field
        name="contextInfo">itemState</field></block></value><value
        name="POSITION2"><block type="lochmueller:blockly:gis_gis_position"
        id="o?3k_qUZ#nKFC},Qf7}H"><field name="LATITUDE">47.535516</field><field
        name="LONGITUDE">-113.470593</field></block></value></block></value><value
        name="itemName"><shadow type="oh_item" id="d%/W,7N*g4YC_hqA5zm{"><field
        name="itemName">GeorgeiPhoneDistanceFrom80Burgundy</field></shadow></value><next><block
        type="oh_event" id="VjelTQ7Zo2Vpsv+a|[Qy"><field
        name="eventType">postUpdate</field><value name="value"><shadow
        type="text"><field name="TEXT">value</field></shadow><block
        type="lochmueller:blockly:gis_gis_distance"
        id=":Qw0O=tFn=x^bq_]G;@*"><value name="POSITION1"><block
        type="oh_context_info" id="Hwnm.#1n6wfgxj8YTh4w"><field
        name="contextInfo">itemState</field></block></value><value
        name="POSITION2"><block type="variables_get"
        id="*773i7=/Z@+D#~u7A,y;"><field name="VAR"
        id="vwjL40[(bStpY%37YS`A">GWatchLocation</field></block></value></block></value><value
        name="itemName"><shadow type="oh_item" id="9WeAN35:.@p/Kqv`V+t["><field
        name="itemName">GeorgeWatchDistanceFromiPhoneiCloud</field></shadow></value></block></next></block></statement><statement
        name="ELSE"><block type="oh_event" id="Qz50VBOqKgKgcF+h%%$O"><field
        name="eventType">postUpdate</field><value name="value"><shadow
        type="text" id="JV~XY:ElTAwunuUFQSl%"><field
        name="TEXT">UNDEF</field></shadow></value><value name="itemName"><shadow
        type="oh_item" id="_Tp)Ek-:+/V4KZXIxL}}"><field
        name="itemName">GeorgeiPhoneDistanceFrom80Burgundy</field></shadow></value><next><block
        type="oh_event" id=".m_`*;pxp2($ksMIfVQz"><field
        name="eventType">postUpdate</field><value name="value"><shadow
        type="text" id="wNb.^@c0;]5xD,O;FpB{"><field
        name="TEXT">UNDEF</field></shadow></value><value name="itemName"><shadow
        type="oh_item" id="Z*D;*4y_R$nnr?!V,+Ym"><field
        name="itemName">GeorgeWatchDistanceFromiPhoneiCloud</field></shadow></value></block></next></block></statement></block></next></block></next></block><block
        type="oh_item" id="KLAZACMlz19T0tulO=gT" disabled="true" x="80"
        y="720"><field
        name="itemName">GeorgesAppleWatchAppleWatchSeries7GPSCellular_Location</field></block></xml>'
      type: application/javascript
      script: >
        var GWatchLocation;


        var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID);


        function geo_distance(position1, position2) {
          var lat1 = position1.latitude;
          var lat2 = position2.latitude;
          var lon1 = position1.longitude;
          var lon2 = position2.longitude;
          var R = 6371e3; // metres
          var o1 = lat1 * Math.PI/180;
          var o2 = lat2 * Math.PI/180;
          var Ao = (lat2-lat1) * Math.PI/180;
          var Ab = (lon2-lon1) * Math.PI/180;
          var a = Math.sin(Ao/2) * Math.sin(Ao/2) +
          Math.cos(o1) * Math.cos(o2) *
          Math.sin(Ab/2) * Math.sin(Ab/2);
          var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
          return R * c;
        }


        function geo_position(latitude, longitude) {
          return {'latitude': latitude, 'longitude': longitude};
        }



        GWatchLocation = itemRegistry.getItem('GeorgesAppleWatchAppleWatchSeries7GPSCellular_Location').getState();

        logger.info(([event.itemName,event.itemState,'\r\n',itemRegistry.getItem('GeorgesiPhoneiPhone13Pro_Location').getName(),itemRegistry.getItem('GeorgesiPhoneiPhone13Pro_Location').getState(),' Variable: ',GWatchLocation].join('')));

        if (event.itemState != 'UNDEF') {
          events.postUpdate('GeorgeiPhoneDistanceFrom80Burgundy', (geo_distance(event.itemState, geo_position(47.535516, -113.470593))));
          events.postUpdate('GeorgeWatchDistanceFromiPhoneiCloud', (geo_distance(event.itemState, GWatchLocation)));
        } else {
          events.postUpdate('GeorgeiPhoneDistanceFrom80Burgundy', 'UNDEF');
          events.postUpdate('GeorgeWatchDistanceFromiPhoneiCloud', 'UNDEF');
        }
    type: script.ScriptAction

It’s working so I’m happy, but I thought I would share my experience because it seems like the block should accept a location type item.

Very nice and mindblowing for new Projects :wink:
If I would start I miss a default block that contains OH coordinates without CnP them from Config.