mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[mybmw] Add home-distance channel (#13093)
* add home-distance channel * use uniformed instead of random gps coordinates Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com>
This commit is contained in:
parent
9c768dc08f
commit
c6778fcb00
@ -340,11 +340,11 @@ GPS location and heading of the vehicle.
|
||||
* Read-only values
|
||||
|
||||
| Channel Label | Channel ID | Type |
|
||||
|-----------------|---------------------|--------------|
|
||||
|---------------------|---------------------|---------------|
|
||||
| GPS Coordinates | gps | Location |
|
||||
| Heading | heading | Number:Angle |
|
||||
| Address | address | String |
|
||||
|
||||
| Distance from Home | home-distance | Number:Length |
|
||||
|
||||
#### Remote Services
|
||||
|
||||
|
@ -114,6 +114,7 @@ public class MyBMWConstants {
|
||||
public static final String GPS = "gps";
|
||||
public static final String HEADING = "heading";
|
||||
public static final String ADDRESS = "address";
|
||||
public static final String HOME_DISTANCE = "home-distance";
|
||||
|
||||
// Status
|
||||
public static final String DOORS = "doors";
|
||||
|
@ -20,6 +20,7 @@ import org.openhab.binding.mybmw.internal.handler.MyBMWBridgeHandler;
|
||||
import org.openhab.binding.mybmw.internal.handler.MyBMWCommandOptionProvider;
|
||||
import org.openhab.binding.mybmw.internal.handler.VehicleHandler;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.LocationProvider;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
@ -42,14 +43,16 @@ import org.osgi.service.component.annotations.Reference;
|
||||
public class MyBMWHandlerFactory extends BaseThingHandlerFactory {
|
||||
private final HttpClientFactory httpClientFactory;
|
||||
private final MyBMWCommandOptionProvider commandOptionProvider;
|
||||
private final LocationProvider locationProvider;
|
||||
private String localeLanguage;
|
||||
|
||||
@Activate
|
||||
public MyBMWHandlerFactory(final @Reference HttpClientFactory hcf, final @Reference MyBMWCommandOptionProvider cop,
|
||||
final @Reference LocaleProvider lp) {
|
||||
final @Reference LocaleProvider localeP, final @Reference LocationProvider locationP) {
|
||||
httpClientFactory = hcf;
|
||||
commandOptionProvider = cop;
|
||||
localeLanguage = lp.getLocale().getLanguage().toLowerCase();
|
||||
locationProvider = locationP;
|
||||
localeLanguage = localeP.getLocale().getLanguage().toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -63,7 +66,8 @@ public class MyBMWHandlerFactory extends BaseThingHandlerFactory {
|
||||
if (THING_TYPE_CONNECTED_DRIVE_ACCOUNT.equals(thingTypeUID)) {
|
||||
return new MyBMWBridgeHandler((Bridge) thing, httpClientFactory, localeLanguage);
|
||||
} else if (SUPPORTED_THING_SET.contains(thingTypeUID)) {
|
||||
VehicleHandler vh = new VehicleHandler(thing, commandOptionProvider, thingTypeUID.getId());
|
||||
VehicleHandler vh = new VehicleHandler(thing, commandOptionProvider, locationProvider,
|
||||
thingTypeUID.getId());
|
||||
return vh;
|
||||
}
|
||||
return null;
|
||||
|
@ -18,6 +18,6 @@ package org.openhab.binding.mybmw.internal.dto.properties;
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class Coordinates {
|
||||
public double latitude;// ": 50.556049,
|
||||
public double longitude;// ": 8.495669
|
||||
public double latitude;
|
||||
public double longitude;
|
||||
}
|
||||
|
@ -20,5 +20,5 @@ package org.openhab.binding.mybmw.internal.dto.properties;
|
||||
public class Location {
|
||||
public Coordinates coordinates;
|
||||
public Address address;
|
||||
public int heading;// ": 222
|
||||
public int heading;
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
import org.openhab.binding.mybmw.internal.utils.Converter;
|
||||
import org.openhab.binding.mybmw.internal.utils.RemoteServiceUtils;
|
||||
import org.openhab.binding.mybmw.internal.utils.VehicleStatusUtils;
|
||||
import org.openhab.core.i18n.LocationProvider;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
@ -55,6 +56,7 @@ import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.ImperialUnits;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
@ -87,17 +89,22 @@ public abstract class VehicleChannelHandler extends BaseThingHandler {
|
||||
protected String selectedSession = Constants.UNDEF;
|
||||
|
||||
protected MyBMWCommandOptionProvider commandOptionProvider;
|
||||
private LocationProvider locationProvider;
|
||||
|
||||
// Data Caches
|
||||
protected Optional<String> vehicleStatusCache = Optional.empty();
|
||||
protected Optional<byte[]> imageCache = Optional.empty();
|
||||
|
||||
public VehicleChannelHandler(Thing thing, MyBMWCommandOptionProvider cop, String type) {
|
||||
public VehicleChannelHandler(Thing thing, MyBMWCommandOptionProvider cop, LocationProvider lp, String type) {
|
||||
super(thing);
|
||||
commandOptionProvider = cop;
|
||||
locationProvider = lp;
|
||||
if (lp.getLocation() == null) {
|
||||
logger.debug("Home location not available");
|
||||
}
|
||||
|
||||
hasFuel = type.equals(VehicleType.CONVENTIONAL.toString()) || type.equals(VehicleType.PLUGIN_HYBRID.toString())
|
||||
|| type.equals(VehicleType.ELECTRIC_REX.toString());
|
||||
|| type.equals(VehicleType.ELECTRIC_REX.toString()) || type.equals(VehicleType.MILD_HYBRID.toString());
|
||||
isElectric = type.equals(VehicleType.PLUGIN_HYBRID.toString())
|
||||
|| type.equals(VehicleType.ELECTRIC_REX.toString()) || type.equals(VehicleType.ELECTRIC.toString());
|
||||
isHybrid = hasFuel && isElectric;
|
||||
@ -448,9 +455,24 @@ public abstract class VehicleChannelHandler extends BaseThingHandler {
|
||||
}
|
||||
|
||||
protected void updatePosition(Location pos) {
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, GPS, PointType
|
||||
.valueOf(Double.toString(pos.coordinates.latitude) + "," + Double.toString(pos.coordinates.longitude)));
|
||||
if (pos.coordinates.latitude < 0) {
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, GPS, UnDefType.UNDEF);
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, HEADING, UnDefType.UNDEF);
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, ADDRESS, UnDefType.UNDEF);
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, HOME_DISTANCE, UnDefType.UNDEF);
|
||||
} else {
|
||||
PointType vehicleLocation = PointType.valueOf(
|
||||
Double.toString(pos.coordinates.latitude) + "," + Double.toString(pos.coordinates.longitude));
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, GPS, vehicleLocation);
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, HEADING, QuantityType.valueOf(pos.heading, Units.DEGREE_ANGLE));
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, ADDRESS, StringType.valueOf(pos.address.formatted));
|
||||
PointType homeLocation = locationProvider.getLocation();
|
||||
if (homeLocation != null) {
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, HOME_DISTANCE,
|
||||
QuantityType.valueOf(vehicleLocation.distanceFrom(homeLocation).intValue(), SIUnits.METRE));
|
||||
} else {
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, HOME_DISTANCE, UnDefType.UNDEF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
import org.openhab.binding.mybmw.internal.utils.Converter;
|
||||
import org.openhab.binding.mybmw.internal.utils.ImageProperties;
|
||||
import org.openhab.binding.mybmw.internal.utils.RemoteServiceUtils;
|
||||
import org.openhab.core.i18n.LocationProvider;
|
||||
import org.openhab.core.io.net.http.HttpUtil;
|
||||
import org.openhab.core.library.types.RawType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
@ -64,8 +65,8 @@ public class VehicleHandler extends VehicleChannelHandler {
|
||||
ChargeSessionsCallback chargeSessionCallback = new ChargeSessionsCallback();
|
||||
ByteResponseCallback imageCallback = new ImageCallback();
|
||||
|
||||
public VehicleHandler(Thing thing, MyBMWCommandOptionProvider cop, String driveTrain) {
|
||||
super(thing, cop, driveTrain);
|
||||
public VehicleHandler(Thing thing, MyBMWCommandOptionProvider cop, LocationProvider lp, String driveTrain) {
|
||||
super(thing, cop, lp, driveTrain);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -238,10 +238,10 @@ public class Converter {
|
||||
}
|
||||
if (v.properties.vehicleLocation == null) {
|
||||
v.properties.vehicleLocation = new Location();
|
||||
v.properties.vehicleLocation.heading = -1;
|
||||
v.properties.vehicleLocation.heading = Constants.INT_UNDEF;
|
||||
v.properties.vehicleLocation.coordinates = new Coordinates();
|
||||
v.properties.vehicleLocation.coordinates.latitude = -1.234;
|
||||
v.properties.vehicleLocation.coordinates.longitude = -9.876;
|
||||
v.properties.vehicleLocation.coordinates.latitude = Constants.INT_UNDEF;
|
||||
v.properties.vehicleLocation.coordinates.longitude = Constants.INT_UNDEF;
|
||||
v.properties.vehicleLocation.address = new Address();
|
||||
v.properties.vehicleLocation.address.formatted = Constants.UNDEF;
|
||||
}
|
||||
|
@ -87,6 +87,8 @@ channel-type.mybmw.front-right-current-channel.label = Tire Pressure Front Right
|
||||
channel-type.mybmw.front-right-target-channel.label = Tire Pressure Front Right Target
|
||||
channel-type.mybmw.gps-channel.label = GPS Coordinates
|
||||
channel-type.mybmw.heading-channel.label = Heading Angle
|
||||
channel-type.mybmw.home-distance-channel.label = Distance from Home
|
||||
channel-type.mybmw.home-distance-channel.description = Computed distance between vehicle and home location
|
||||
channel-type.mybmw.hood-channel.label = Hood
|
||||
channel-type.mybmw.image-view-channel.label = Image Viewport
|
||||
channel-type.mybmw.image-view-channel.command.option.VehicleStatus = Front Side View
|
||||
|
@ -10,6 +10,7 @@
|
||||
<channel id="gps" typeId="gps-channel"/>
|
||||
<channel id="heading" typeId="heading-channel"/>
|
||||
<channel id="address" typeId="address-channel"/>
|
||||
<channel id="home-distance" typeId="home-distance-channel"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
</thing:thing-descriptions>
|
||||
|
@ -16,4 +16,10 @@
|
||||
<item-type>String</item-type>
|
||||
<label>Address</label>
|
||||
</channel-type>
|
||||
<channel-type id="home-distance-channel">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Distance from Home</label>
|
||||
<description>Computed distance between vehicle and home location</description>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
|
@ -27,6 +27,7 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWConstants.VehicleType;
|
||||
import org.openhab.binding.mybmw.internal.dto.properties.CBS;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
|
||||
import org.openhab.binding.mybmw.internal.handler.VehicleTests;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
import org.openhab.binding.mybmw.internal.utils.Converter;
|
||||
import org.openhab.binding.mybmw.internal.utils.VehicleStatusUtils;
|
||||
@ -36,6 +37,7 @@ import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.ImperialUnits;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.types.State;
|
||||
@ -60,7 +62,7 @@ public class StatusWrapper {
|
||||
|
||||
public StatusWrapper(String type, String statusJson) {
|
||||
hasFuel = type.equals(VehicleType.CONVENTIONAL.toString()) || type.equals(VehicleType.PLUGIN_HYBRID.toString())
|
||||
|| type.equals(VehicleType.ELECTRIC_REX.toString());
|
||||
|| type.equals(VehicleType.ELECTRIC_REX.toString()) || type.equals(VehicleType.MILD_HYBRID.toString());
|
||||
isElectric = type.equals(VehicleType.PLUGIN_HYBRID.toString())
|
||||
|| type.equals(VehicleType.ELECTRIC_REX.toString()) || type.equals(VehicleType.ELECTRIC.toString());
|
||||
isHybrid = hasFuel && isElectric;
|
||||
@ -246,20 +248,22 @@ public class StatusWrapper {
|
||||
assertEquals(expected.toString(), dtt.toString(), "Last Update");
|
||||
break;
|
||||
case GPS:
|
||||
assertTrue(state instanceof PointType);
|
||||
if (state instanceof PointType) {
|
||||
pt = (PointType) state;
|
||||
assertNotNull(vehicle.properties.vehicleLocation);
|
||||
assertEquals(
|
||||
PointType.valueOf(Double.toString(vehicle.properties.vehicleLocation.coordinates.latitude) + ","
|
||||
+ Double.toString(vehicle.properties.vehicleLocation.coordinates.longitude)),
|
||||
PointType.valueOf(Double.toString(vehicle.properties.vehicleLocation.coordinates.latitude)
|
||||
+ "," + Double.toString(vehicle.properties.vehicleLocation.coordinates.longitude)),
|
||||
pt, "Coordinates");
|
||||
} // else no check needed
|
||||
break;
|
||||
case HEADING:
|
||||
assertTrue(state instanceof QuantityType);
|
||||
if (state instanceof QuantityType) {
|
||||
qt = ((QuantityType) state);
|
||||
assertEquals(Units.DEGREE_ANGLE, qt.getUnit(), "Angle Unit");
|
||||
assertNotNull(vehicle.properties.vehicleLocation);
|
||||
assertEquals(vehicle.properties.vehicleLocation.heading, qt.intValue(), 0.01, "Heading");
|
||||
} // else no check needed
|
||||
break;
|
||||
case RANGE_RADIUS_ELECTRIC:
|
||||
assertTrue(state instanceof QuantityType);
|
||||
@ -568,10 +572,22 @@ public class StatusWrapper {
|
||||
}
|
||||
break;
|
||||
case ADDRESS:
|
||||
assertTrue(state instanceof StringType);
|
||||
if (state instanceof StringType) {
|
||||
st = (StringType) state;
|
||||
assertEquals(st.toFullString(), vehicle.properties.vehicleLocation.address.formatted,
|
||||
"Location Address");
|
||||
} // else no check needed
|
||||
break;
|
||||
case HOME_DISTANCE:
|
||||
if (state instanceof QuantityType) {
|
||||
qt = (QuantityType) state;
|
||||
PointType vehicleLocation = PointType
|
||||
.valueOf(Double.toString(vehicle.properties.vehicleLocation.coordinates.latitude) + ","
|
||||
+ Double.toString(vehicle.properties.vehicleLocation.coordinates.longitude));
|
||||
int distance = vehicleLocation.distanceFrom(VehicleTests.HOME_LOCATION).intValue();
|
||||
assertEquals(qt.intValue(), distance, "Distance from Home");
|
||||
assertEquals(qt.getUnit(), SIUnits.METRE, "Distance from Home Unit");
|
||||
} // else no check needed
|
||||
break;
|
||||
case RAW:
|
||||
// don't assert raw channel
|
||||
|
@ -62,7 +62,7 @@ public class VehicleStatusTest {
|
||||
assertEquals("BMW", v.brand, "Car brand");
|
||||
assertEquals(true, v.properties.areDoorsClosed, "Doors Closed");
|
||||
assertEquals(76, v.properties.electricRange.distance.value, "Electric Range");
|
||||
assertEquals(6.789, v.properties.vehicleLocation.coordinates.longitude, 0.1, "Location lon");
|
||||
assertEquals(9.876, v.properties.vehicleLocation.coordinates.longitude, 0.1, "Location lon");
|
||||
assertEquals("immediateCharging", v.status.chargingProfile.chargingMode, "Charging Mode");
|
||||
assertEquals(2, v.status.chargingProfile.getTimerId(2).id, "Timer ID");
|
||||
assertEquals("[sunday]", v.status.chargingProfile.getTimerId(2).timerWeekDays.toString(), "Timer Weekdays");
|
||||
|
@ -28,6 +28,7 @@ import org.openhab.binding.mybmw.internal.VehicleConfiguration;
|
||||
import org.openhab.binding.mybmw.internal.dto.ChargeStatisticWrapper;
|
||||
import org.openhab.binding.mybmw.internal.util.FileReader;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
import org.openhab.core.i18n.LocationProvider;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
@ -73,7 +74,8 @@ public class ChargeStatisticsTest {
|
||||
Thing thing = mock(Thing.class);
|
||||
when(thing.getUID()).thenReturn(new ThingUID("testbinding", "test"));
|
||||
MyBMWCommandOptionProvider cop = mock(MyBMWCommandOptionProvider.class);
|
||||
cch = new VehicleHandler(thing, cop, type);
|
||||
LocationProvider locationProvider = mock(LocationProvider.class);
|
||||
cch = new VehicleHandler(thing, cop, locationProvider, type);
|
||||
VehicleConfiguration vc = new VehicleConfiguration();
|
||||
vc.vin = Constants.ANONYMOUS;
|
||||
Optional<VehicleConfiguration> ovc = Optional.of(vc);
|
||||
|
@ -20,6 +20,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.openhab.core.i18n.LocationProvider;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
@ -60,7 +61,8 @@ public class ErrorResponseTest {
|
||||
Thing thing = mock(Thing.class);
|
||||
when(thing.getUID()).thenReturn(new ThingUID("testbinding", "test"));
|
||||
MyBMWCommandOptionProvider cop = mock(MyBMWCommandOptionProvider.class);
|
||||
cch = new VehicleHandler(thing, cop, type);
|
||||
LocationProvider locationProvider = mock(LocationProvider.class);
|
||||
cch = new VehicleHandler(thing, cop, locationProvider, type);
|
||||
tc = mock(ThingHandlerCallback.class);
|
||||
cch.setCallback(tc);
|
||||
channelCaptor = ArgumentCaptor.forClass(ChannelUID.class);
|
||||
|
@ -28,6 +28,8 @@ import org.openhab.binding.mybmw.internal.VehicleConfiguration;
|
||||
import org.openhab.binding.mybmw.internal.dto.StatusWrapper;
|
||||
import org.openhab.binding.mybmw.internal.util.FileReader;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
import org.openhab.core.i18n.LocationProvider;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
@ -57,10 +59,10 @@ public class VehicleTests {
|
||||
private static final int CHECK_AVAILABLE = 3;
|
||||
private static final int SERVICE_AVAILABLE = 3;
|
||||
private static final int SERVICE_EMPTY = 3;
|
||||
private static final int LOCATION = 3;
|
||||
private static final int LOCATION = 4;
|
||||
private static final int CHARGE_PROFILE = 44;
|
||||
private static final int TIRES = 8;
|
||||
|
||||
public static final PointType HOME_LOCATION = new PointType("54.321,9.876");
|
||||
@Nullable
|
||||
ArgumentCaptor<ChannelUID> channelCaptor;
|
||||
@Nullable
|
||||
@ -83,7 +85,9 @@ public class VehicleTests {
|
||||
Thing thing = mock(Thing.class);
|
||||
when(thing.getUID()).thenReturn(new ThingUID("testbinding", "test"));
|
||||
MyBMWCommandOptionProvider cop = mock(MyBMWCommandOptionProvider.class);
|
||||
cch = new VehicleHandler(thing, cop, type);
|
||||
LocationProvider locationProvider = mock(LocationProvider.class);
|
||||
when(locationProvider.getLocation()).thenReturn(HOME_LOCATION);
|
||||
cch = new VehicleHandler(thing, cop, locationProvider, type);
|
||||
VehicleConfiguration vc = new VehicleConfiguration();
|
||||
vc.vin = vin;
|
||||
Optional<VehicleConfiguration> ovc = Optional.of(vc);
|
||||
@ -332,9 +336,7 @@ public class VehicleTests {
|
||||
logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
|
||||
setup(VehicleType.MILD_HYBRID.toString(), "anonymous");
|
||||
String content = FileReader.readFileInString("src/test/resources/responses/G21/340i.json");
|
||||
assertTrue(testVehicle(content, 38, Optional.empty()));
|
||||
// assertTrue(testVehicle(content,
|
||||
// STATUS_CONV + DOORS + RANGE_CONV + SERVICE_AVAILABLE + CHECK_EMPTY + LOCATION + TIRES,
|
||||
// Optional.empty()));
|
||||
assertTrue(testVehicle(content,
|
||||
STATUS_CONV + DOORS + RANGE_CONV + LOCATION + SERVICE_EMPTY + CHECK_EMPTY + TIRES, Optional.empty()));
|
||||
}
|
||||
}
|
||||
|
@ -173,8 +173,8 @@
|
||||
],
|
||||
"vehicleLocation": {
|
||||
"coordinates": {
|
||||
"latitude": 1.2345,
|
||||
"longitude": 6.789
|
||||
"latitude": 54.321,
|
||||
"longitude": 9.876
|
||||
},
|
||||
"address": {
|
||||
"formatted": "anonymous"
|
||||
|
Loading…
Reference in New Issue
Block a user