# Geographic information system

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

## Resources

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

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
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="``Arfe,D7;]qDG}I%dDd"><field
type="oh_log" id="fn:IL[;|E4MaQBE\$+hQK"><field
type="text_join" id="];D!jCb!h-u*\$itqP,L`"><mutation
id="V,a4+SG{J]!NQzp5kH[0"><field
name="contextInfo">itemName</field></block></value><value
id="Bcm,t!^|mP.{+t2uYPiD"><field
name="contextInfo">itemState</field></block></value><value
type="oh_getitem_attribute" id="!Pc/ML[!xE}Idzf{4J32"><mutation
attributeName="Name"></mutation><field
type="oh_getitem" id="ve~F[Fvu%o5BdFfHtY.I"><value
id="vf}KCdeqp}MYgI?F)w/R"><field
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
type="text" id=",HT]dv%XT]h\$*YXkC[5`"><field
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
type="oh_event" id="VjelTQ7Zo2Vpsv+a|[Qy"><field
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="ELSE"><block type="oh_event" id="Qz50VBOqKgKgcF+h%%\$O"><field
type="text" id="JV~XY:ElTAwunuUFQSl%"><field
type="oh_item" id="_Tp)Ek-:+/V4KZXIxL}}"><field
type="oh_event" id=".m_`*;pxp2(\$ksMIfVQz"><field
type="text" id="wNb.^@c0;]5xD,O;FpB{"><field
type="oh_item" id="Z*D;*4y_R\$nnr?!V,+Ym"><field
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
If I would start I miss a default block that contains OH coordinates without CnP them from Config.