Added custom serializer/deserializer logic for perimeter definitions
This commit is contained in:
		
							parent
							
								
									cc3ecfc027
								
							
						
					
					
						commit
						29934d08bd
					
				@ -622,13 +622,9 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
 | 
			
		||||
        Device device = createDevice("GF Device", "sn-geo-1");
 | 
			
		||||
 | 
			
		||||
        // Allowed zone polygon (square)
 | 
			
		||||
        String allowedPolygon = """
 | 
			
		||||
                {"type":"POLYGON","polygonsDefinition":"[[50.472000, 30.504000], [50.472000, 30.506000], [50.474000, 30.506000], [50.474000, 30.504000]]"}
 | 
			
		||||
                """;
 | 
			
		||||
        String allowedPolygon = "[[50.472000, 30.504000], [50.472000, 30.506000], [50.474000, 30.506000], [50.474000, 30.504000]]";
 | 
			
		||||
        // Restricted zone polygon (square)
 | 
			
		||||
        String restrictedPolygon = """
 | 
			
		||||
                {"type":"POLYGON","polygonsDefinition":"[[50.475000, 30.510000], [50.475000, 30.512000], [50.477000, 30.512000], [50.477000, 30.510000]]"}
 | 
			
		||||
                """;
 | 
			
		||||
        String restrictedPolygon = "[[50.475000, 30.510000], [50.475000, 30.512000], [50.477000, 30.512000], [50.477000, 30.510000]]";
 | 
			
		||||
 | 
			
		||||
        Asset allowedZoneAsset = createAsset("Allowed Zone", null);
 | 
			
		||||
        doPost("/api/plugins/telemetry/ASSET/" + allowedZoneAsset.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE,
 | 
			
		||||
@ -768,9 +764,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
 | 
			
		||||
        Device device = createDevice("GF Device dyn", "sn-geo-dyn-1");
 | 
			
		||||
 | 
			
		||||
        // Allowed Zone A: covers initial point (ENTERED)
 | 
			
		||||
        String allowedPolygonA = """
 | 
			
		||||
                {"type":"POLYGON","polygonsDefinition":"[[50.472000, 30.504000], [50.472000, 30.506000], [50.474000, 30.506000], [50.474000, 30.504000]]"}
 | 
			
		||||
                """;
 | 
			
		||||
        String allowedPolygonA = "[[50.472000, 30.504000], [50.472000, 30.506000], [50.474000, 30.506000], [50.474000, 30.504000]]";
 | 
			
		||||
 | 
			
		||||
        Asset allowedZoneA = createAsset("Allowed Zone A", null);
 | 
			
		||||
        doPost("/api/plugins/telemetry/ASSET/" + allowedZoneA.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE,
 | 
			
		||||
@ -863,9 +857,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
        // --- Create Allowed Zone B covering the CURRENT location ---
 | 
			
		||||
        String allowedPolygonB = """
 | 
			
		||||
                {"type":"POLYGON","polygonsDefinition":"[[50.475500, 30.510500], [50.475500, 30.511500], [50.476500, 30.511500], [50.476500, 30.510500]]"}
 | 
			
		||||
                """;
 | 
			
		||||
        String allowedPolygonB = "[[50.475500, 30.510500], [50.475500, 30.511500], [50.476500, 30.511500], [50.476500, 30.510500]]";
 | 
			
		||||
 | 
			
		||||
        Asset allowedZoneB = createAsset("Allowed Zone B", null);
 | 
			
		||||
        doPost("/api/plugins/telemetry/ASSET/" + allowedZoneB.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE,
 | 
			
		||||
 | 
			
		||||
@ -73,13 +73,11 @@ public class GeofencingCalculatedFieldStateTest {
 | 
			
		||||
    private final SingleValueArgumentEntry latitudeArgEntry = new SingleValueArgumentEntry(System.currentTimeMillis() - 10, new DoubleDataEntry("latitude", 50.4730), 145L);
 | 
			
		||||
    private final SingleValueArgumentEntry longitudeArgEntry = new SingleValueArgumentEntry(System.currentTimeMillis() - 6, new DoubleDataEntry("longitude", 30.5050), 165L);
 | 
			
		||||
 | 
			
		||||
    private final JsonDataEntry allowedZoneDataEntry = new JsonDataEntry("zone", """
 | 
			
		||||
            {"type":"POLYGON","polygonsDefinition":"[[50.472000, 30.504000], [50.472000, 30.506000], [50.474000, 30.506000], [50.474000, 30.504000]]"}""");
 | 
			
		||||
    private final JsonDataEntry allowedZoneDataEntry = new JsonDataEntry("zone", "[[50.472000, 30.504000], [50.472000, 30.506000], [50.474000, 30.506000], [50.474000, 30.504000]]");
 | 
			
		||||
    private final BaseAttributeKvEntry allowedZoneAttributeKvEntry = new BaseAttributeKvEntry(allowedZoneDataEntry, System.currentTimeMillis(), 0L);
 | 
			
		||||
    private final GeofencingArgumentEntry geofencingAllowedZoneArgEntry = new GeofencingArgumentEntry(Map.of(ZONE_1_ID, allowedZoneAttributeKvEntry));
 | 
			
		||||
 | 
			
		||||
    private final JsonDataEntry restrictedZoneDataEntry = new JsonDataEntry("zone", """
 | 
			
		||||
            {"type":"POLYGON","polygonsDefinition":"[[50.475000, 30.510000], [50.475000, 30.512000], [50.477000, 30.512000], [50.477000, 30.510000]]"}""");
 | 
			
		||||
    private final JsonDataEntry restrictedZoneDataEntry = new JsonDataEntry("zone", "[[50.475000, 30.510000], [50.475000, 30.512000], [50.477000, 30.512000], [50.477000, 30.510000]]");
 | 
			
		||||
    private final BaseAttributeKvEntry restrictedZoneAttributeKvEntry = new BaseAttributeKvEntry(restrictedZoneDataEntry, System.currentTimeMillis(), 0L);
 | 
			
		||||
    private final GeofencingArgumentEntry geofencingRestrictedZoneArgEntry = new GeofencingArgumentEntry(Map.of(ZONE_2_ID, restrictedZoneAttributeKvEntry));
 | 
			
		||||
 | 
			
		||||
@ -219,6 +217,7 @@ public class GeofencingCalculatedFieldStateTest {
 | 
			
		||||
        assertThat(state.isReady()).isFalse();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO: test different reporting strategies
 | 
			
		||||
    @Test
 | 
			
		||||
    void testPerformCalculation() throws ExecutionException, InterruptedException {
 | 
			
		||||
        state.arguments = new HashMap<>(Map.of(
 | 
			
		||||
@ -241,8 +240,9 @@ public class GeofencingCalculatedFieldStateTest {
 | 
			
		||||
        assertThat(result.getScope()).isEqualTo(output.getScope());
 | 
			
		||||
        assertThat(result.getResult()).isEqualTo(
 | 
			
		||||
                JacksonUtil.newObjectNode()
 | 
			
		||||
                        .put("allowedZoneEvent", "ENTERED")
 | 
			
		||||
                        .put("restrictedZoneEvent", "OUTSIDE")
 | 
			
		||||
                        .put("allowedZonesEvent", "ENTERED")
 | 
			
		||||
                        .put("allowedZonesStatus", "INSIDE")
 | 
			
		||||
                        .put("restrictedZonesStatus", "OUTSIDE")
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        SingleValueArgumentEntry newLatitude = new SingleValueArgumentEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 50.4760), 146L);
 | 
			
		||||
@ -258,8 +258,10 @@ public class GeofencingCalculatedFieldStateTest {
 | 
			
		||||
        assertThat(result2.getScope()).isEqualTo(output.getScope());
 | 
			
		||||
        assertThat(result2.getResult()).isEqualTo(
 | 
			
		||||
                JacksonUtil.newObjectNode()
 | 
			
		||||
                        .put("allowedZoneEvent", "LEFT")
 | 
			
		||||
                        .put("restrictedZoneEvent", "ENTERED")
 | 
			
		||||
                        .put("allowedZonesEvent", "LEFT")
 | 
			
		||||
                        .put("allowedZonesStatus", "OUTSIDE")
 | 
			
		||||
                        .put("restrictedZonesEvent", "ENTERED")
 | 
			
		||||
                        .put("restrictedZonesStatus", "INSIDE")
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -36,12 +36,10 @@ public class GeofencingValueArgumentEntryTest {
 | 
			
		||||
    private final AssetId ZONE_1_ID = new AssetId(UUID.fromString("c0e3031c-7df1-45e4-9590-cfd621a4d714"));
 | 
			
		||||
    private final AssetId ZONE_2_ID = new AssetId(UUID.fromString("e7da6200-2096-4038-a343-ade9ea4fa3e4"));
 | 
			
		||||
 | 
			
		||||
    private final JsonDataEntry allowedZoneDataEntry = new JsonDataEntry("zone", """
 | 
			
		||||
            {"type":"POLYGON","polygonsDefinition":"[[50.472000, 30.504000], [50.472000, 30.506000], [50.474000, 30.506000], [50.474000, 30.504000]]"}""");
 | 
			
		||||
    private final JsonDataEntry allowedZoneDataEntry = new JsonDataEntry("zone", "[[50.472000, 30.504000], [50.472000, 30.506000], [50.474000, 30.506000], [50.474000, 30.504000]]");
 | 
			
		||||
    private final BaseAttributeKvEntry allowedZoneAttributeKvEntry = new BaseAttributeKvEntry(allowedZoneDataEntry, 363L, 155L);
 | 
			
		||||
 | 
			
		||||
    private final JsonDataEntry restrictedZoneDataEntry = new JsonDataEntry("zone", """
 | 
			
		||||
            {"type":"POLYGON","polygonsDefinition":"[[50.475000, 30.510000], [50.475000, 30.512000], [50.477000, 30.512000], [50.477000, 30.510000]]"}""");
 | 
			
		||||
    private final JsonDataEntry restrictedZoneDataEntry = new JsonDataEntry("zone", "[[50.475000, 30.510000], [50.475000, 30.512000], [50.477000, 30.512000], [50.477000, 30.510000]]");
 | 
			
		||||
    private final BaseAttributeKvEntry restrictedZoneAttributeKvEntry = new BaseAttributeKvEntry(restrictedZoneDataEntry, 363L, 155L);
 | 
			
		||||
 | 
			
		||||
    private GeofencingArgumentEntry entry;
 | 
			
		||||
@ -72,8 +70,7 @@ public class GeofencingValueArgumentEntryTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    void testUpdateEntryWithTheSameTs() {
 | 
			
		||||
        BaseAttributeKvEntry differentValueSameTs = new BaseAttributeKvEntry(new JsonDataEntry("zone", """
 | 
			
		||||
            {"type":"POLYGON","polygonsDefinition":"[[50.472001, 30.504001], [50.472001, 30.506001], [50.474001, 30.506001], [50.474001, 30.504001]]"}"""), 363L, 156L);
 | 
			
		||||
        BaseAttributeKvEntry differentValueSameTs = new BaseAttributeKvEntry(new JsonDataEntry("zone", "[[50.472001, 30.504001], [50.472001, 30.506001], [50.474001, 30.506001], [50.474001, 30.504001]]"), 363L, 156L);
 | 
			
		||||
        var updated = new GeofencingArgumentEntry(Map.of(ZONE_1_ID, differentValueSameTs, ZONE_2_ID, restrictedZoneAttributeKvEntry));
 | 
			
		||||
        assertThat(entry.updateEntry(updated)).isFalse();
 | 
			
		||||
    }
 | 
			
		||||
@ -81,8 +78,7 @@ public class GeofencingValueArgumentEntryTest {
 | 
			
		||||
    @Test
 | 
			
		||||
    @SuppressWarnings("unchecked")
 | 
			
		||||
    void testUpdateEntryWhenNewVersionIsNull() {
 | 
			
		||||
        BaseAttributeKvEntry differentValueNewVersionIsNull = new BaseAttributeKvEntry(new JsonDataEntry("zone", """
 | 
			
		||||
            {"type":"POLYGON","polygonsDefinition":"[[50.472001, 30.504001], [50.472001, 30.506001], [50.474001, 30.506001], [50.474001, 30.504001]]"}"""), 364L, null);
 | 
			
		||||
        BaseAttributeKvEntry differentValueNewVersionIsNull = new BaseAttributeKvEntry(new JsonDataEntry("zone", "[[50.472001, 30.504001], [50.472001, 30.506001], [50.474001, 30.506001], [50.474001, 30.504001]]"), 364L, null);
 | 
			
		||||
        var updated = new GeofencingArgumentEntry(Map.of(ZONE_1_ID, differentValueNewVersionIsNull, ZONE_2_ID, restrictedZoneAttributeKvEntry));
 | 
			
		||||
 | 
			
		||||
        assertThat(entry.updateEntry(updated)).isTrue();
 | 
			
		||||
@ -104,8 +100,7 @@ public class GeofencingValueArgumentEntryTest {
 | 
			
		||||
    @Test
 | 
			
		||||
    @SuppressWarnings("unchecked")
 | 
			
		||||
    void testUpdateEntryWhenNewVersionIsGreaterThanCurrent() {
 | 
			
		||||
        BaseAttributeKvEntry differentValueNewVersionIsSet = new BaseAttributeKvEntry(new JsonDataEntry("zone", """
 | 
			
		||||
            {"type":"POLYGON","polygonsDefinition":"[[50.472001, 30.504001], [50.472001, 30.506001], [50.474001, 30.506001], [50.474001, 30.504001]]"}"""), 364L, 156L);
 | 
			
		||||
        BaseAttributeKvEntry differentValueNewVersionIsSet = new BaseAttributeKvEntry(new JsonDataEntry("zone", "[[50.472001, 30.504001], [50.472001, 30.506001], [50.474001, 30.506001], [50.474001, 30.504001]]"), 364L, 156L);
 | 
			
		||||
        var updated = new GeofencingArgumentEntry(Map.of(ZONE_1_ID, differentValueNewVersionIsSet, ZONE_2_ID, restrictedZoneAttributeKvEntry));
 | 
			
		||||
 | 
			
		||||
        assertThat(entry.updateEntry(updated)).isTrue();
 | 
			
		||||
@ -126,8 +121,7 @@ public class GeofencingValueArgumentEntryTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    void testUpdateEntryWhenNewVersionIsLessThanCurrent() {
 | 
			
		||||
        BaseAttributeKvEntry differentValueNewVersionIsSet = new BaseAttributeKvEntry(new JsonDataEntry("zone", """
 | 
			
		||||
            {"type":"POLYGON","polygonsDefinition":"[[50.472001, 30.504001], [50.472001, 30.506001], [50.474001, 30.506001], [50.474001, 30.504001]]"}"""), 364L, 154L);
 | 
			
		||||
        BaseAttributeKvEntry differentValueNewVersionIsSet = new BaseAttributeKvEntry(new JsonDataEntry("zone", "[[50.472001, 30.504001], [50.472001, 30.506001], [50.474001, 30.506001], [50.474001, 30.504001]]"), 364L, 154L);
 | 
			
		||||
        var updated = new GeofencingArgumentEntry(Map.of(ZONE_1_ID, differentValueNewVersionIsSet, ZONE_2_ID, restrictedZoneAttributeKvEntry));
 | 
			
		||||
 | 
			
		||||
        assertThat(entry.updateEntry(updated)).isFalse();
 | 
			
		||||
@ -152,8 +146,7 @@ public class GeofencingValueArgumentEntryTest {
 | 
			
		||||
    @Test
 | 
			
		||||
    void testUpdateEntryWithNewZone() {
 | 
			
		||||
        final AssetId NEW_ZONE_ID = new AssetId(UUID.fromString("a3eacf1a-6af3-4e9f-87c4-502bb25c7dc3"));
 | 
			
		||||
        BaseAttributeKvEntry newZone = new BaseAttributeKvEntry(new JsonDataEntry("zone", """
 | 
			
		||||
            {"type":"POLYGON","polygonsDefinition":"[[50.472001, 30.504001], [50.472001, 30.506001], [50.474001, 30.506001], [50.474001, 30.504001]]"}"""), 364L, 156L);
 | 
			
		||||
        BaseAttributeKvEntry newZone = new BaseAttributeKvEntry(new JsonDataEntry("zone", "[[50.472001, 30.504001], [50.472001, 30.506001], [50.474001, 30.506001], [50.474001, 30.504001]]"), 364L, 156L);
 | 
			
		||||
        var updated = new GeofencingArgumentEntry(Map.of(ZONE_1_ID, allowedZoneAttributeKvEntry, ZONE_2_ID, restrictedZoneAttributeKvEntry, NEW_ZONE_ID, newZone));
 | 
			
		||||
        assertThat(entry.updateEntry(updated)).isTrue();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -38,9 +38,7 @@ public class GeofencingZoneStateTest {
 | 
			
		||||
 | 
			
		||||
    @BeforeEach
 | 
			
		||||
    void setUp() {
 | 
			
		||||
        String POLYGON = """
 | 
			
		||||
                {"type":"POLYGON","polygonsDefinition":"[[50.472000, 30.504000], [50.472000, 30.506000], [50.474000, 30.506000], [50.474000, 30.504000]]"}
 | 
			
		||||
                """;
 | 
			
		||||
        String POLYGON = "[[50.472000, 30.504000], [50.472000, 30.506000], [50.474000, 30.506000], [50.474000, 30.504000]]";
 | 
			
		||||
        state = new GeofencingZoneState(ZONE_ID, new BaseAttributeKvEntry(new JsonDataEntry("zone", POLYGON), 100L, 1L));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -70,8 +70,8 @@ class CalculatedFieldUtilsTest {
 | 
			
		||||
        AssetId z1 = new AssetId(zoneId1);
 | 
			
		||||
        AssetId z2 = new AssetId(zoneId2);
 | 
			
		||||
 | 
			
		||||
        JsonDataEntry zone1 = new JsonDataEntry("zone", "{\"type\":\"POLYGON\",\"polygonsDefinition\":\"[[50.472000, 30.504000], [50.472000, 30.506000], [50.474000, 30.506000], [50.474000, 30.504000]]\"}");
 | 
			
		||||
        JsonDataEntry zone2 = new JsonDataEntry("zone", "{\"type\":\"POLYGON\",\"polygonsDefinition\":\"[[50.475000, 30.510000], [50.475000, 30.512000], [50.477000, 30.512000], [50.477000, 30.510000]]\"}");
 | 
			
		||||
        JsonDataEntry zone1 = new JsonDataEntry("zone", "[[50.472000, 30.504000], [50.472000, 30.506000], [50.474000, 30.506000], [50.474000, 30.504000]]");
 | 
			
		||||
        JsonDataEntry zone2 = new JsonDataEntry("zone", "[[50.475000, 30.510000], [50.475000, 30.512000], [50.477000, 30.512000], [50.477000, 30.510000]]");
 | 
			
		||||
 | 
			
		||||
        BaseAttributeKvEntry zone1PerimeterAttribute = new BaseAttributeKvEntry(zone1, System.currentTimeMillis(), 0L);
 | 
			
		||||
        BaseAttributeKvEntry zone2PerimeterAttribute = new BaseAttributeKvEntry(zone2, System.currentTimeMillis(), 0L);
 | 
			
		||||
 | 
			
		||||
@ -83,7 +83,7 @@ public class GeofencingCalculatedFieldConfiguration extends BaseCalculatedFieldC
 | 
			
		||||
 | 
			
		||||
    private void validateZoneGroupConfigurations(Map<String, Argument> zoneGroupsArguments) {
 | 
			
		||||
        if (zoneGroupReportStrategies == null || zoneGroupReportStrategies.isEmpty()) {
 | 
			
		||||
            throw new IllegalArgumentException("Zone groups configuration should be specified!");
 | 
			
		||||
            throw new IllegalArgumentException("Zone groups reporting strategies should be specified!");
 | 
			
		||||
        }
 | 
			
		||||
        zoneGroupsArguments.forEach((zoneGroupName, zoneGroupArgument) -> {
 | 
			
		||||
            GeofencingReportStrategy geofencingReportStrategy = zoneGroupReportStrategies.get(zoneGroupName);
 | 
			
		||||
 | 
			
		||||
@ -251,7 +251,7 @@ public class GeofencingCalculatedFieldConfigurationTest {
 | 
			
		||||
 | 
			
		||||
        assertThatThrownBy(cfg::validate)
 | 
			
		||||
                .isInstanceOf(IllegalArgumentException.class)
 | 
			
		||||
                .hasMessage("Zone groups configuration should be specified!");
 | 
			
		||||
                .hasMessage("Zone groups reporting strategies should be specified!");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
 | 
			
		||||
@ -20,10 +20,9 @@ import lombok.Data;
 | 
			
		||||
@Data
 | 
			
		||||
public class CirclePerimeterDefinition implements PerimeterDefinition {
 | 
			
		||||
 | 
			
		||||
    private Double centerLatitude;
 | 
			
		||||
    private Double centerLongitude;
 | 
			
		||||
    private Double range;
 | 
			
		||||
    private RangeUnit rangeUnit;
 | 
			
		||||
    private final Double latitude;
 | 
			
		||||
    private final Double longitude;
 | 
			
		||||
    private final Double radius;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public PerimeterType getType() {
 | 
			
		||||
@ -32,9 +31,8 @@ public class CirclePerimeterDefinition implements PerimeterDefinition {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean checkMatches(Coordinates entityCoordinates) {
 | 
			
		||||
        Coordinates perimeterCoordinates = new Coordinates(centerLatitude, centerLongitude);
 | 
			
		||||
        return range > GeoUtil.distance(entityCoordinates, perimeterCoordinates, rangeUnit);
 | 
			
		||||
        Coordinates perimeterCoordinates = new Coordinates(latitude, longitude);
 | 
			
		||||
        return radius > GeoUtil.distance(entityCoordinates, perimeterCoordinates, RangeUnit.METER);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -17,19 +17,14 @@ package org.thingsboard.common.util.geo;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonIgnore;
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
 | 
			
		||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 | 
			
		||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 | 
			
		||||
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
 | 
			
		||||
@JsonTypeInfo(
 | 
			
		||||
        use = JsonTypeInfo.Id.NAME,
 | 
			
		||||
        include = JsonTypeInfo.As.PROPERTY,
 | 
			
		||||
        property = "type")
 | 
			
		||||
@JsonSubTypes({
 | 
			
		||||
        @JsonSubTypes.Type(value = PolygonPerimeterDefinition.class, name = "POLYGON"),
 | 
			
		||||
        @JsonSubTypes.Type(value = CirclePerimeterDefinition.class, name = "CIRCLE")})
 | 
			
		||||
@JsonIgnoreProperties(ignoreUnknown = true)
 | 
			
		||||
@JsonDeserialize(using = PerimeterDefinitionDeserializer.class)
 | 
			
		||||
@JsonSerialize(using = PerimeterDefinitionSerializer.class)
 | 
			
		||||
public interface PerimeterDefinition extends Serializable {
 | 
			
		||||
 | 
			
		||||
    @JsonIgnore
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,48 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2025 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.common.util.geo;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.core.JsonParser;
 | 
			
		||||
import com.fasterxml.jackson.core.ObjectCodec;
 | 
			
		||||
import com.fasterxml.jackson.databind.DeserializationContext;
 | 
			
		||||
import com.fasterxml.jackson.databind.JsonDeserializer;
 | 
			
		||||
import com.fasterxml.jackson.databind.JsonNode;
 | 
			
		||||
import com.fasterxml.jackson.databind.ObjectMapper;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
public class PerimeterDefinitionDeserializer extends JsonDeserializer<PerimeterDefinition> {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public PerimeterDefinition deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
 | 
			
		||||
        ObjectCodec codec = p.getCodec();
 | 
			
		||||
        JsonNode node = codec.readTree(p);
 | 
			
		||||
 | 
			
		||||
        if (node.isObject()) {
 | 
			
		||||
            double latitude = node.get("latitude").asDouble();
 | 
			
		||||
            double longitude = node.get("longitude").asDouble();
 | 
			
		||||
            double radius = node.get("radius").asDouble();
 | 
			
		||||
            return new CirclePerimeterDefinition(latitude, longitude, radius);
 | 
			
		||||
        }
 | 
			
		||||
        if (node.isArray()) {
 | 
			
		||||
            ObjectMapper mapper = (ObjectMapper) p.getCodec();
 | 
			
		||||
            String polygonStrDefinition = mapper.writeValueAsString(node);
 | 
			
		||||
            return new PolygonPerimeterDefinition(polygonStrDefinition);
 | 
			
		||||
        }
 | 
			
		||||
        throw new IOException("Failed to deserialize PerimeterDefinition from node: " + node);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,49 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2025 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.common.util.geo;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.core.JsonGenerator;
 | 
			
		||||
import com.fasterxml.jackson.databind.JsonSerializer;
 | 
			
		||||
import com.fasterxml.jackson.databind.ObjectMapper;
 | 
			
		||||
import com.fasterxml.jackson.databind.SerializerProvider;
 | 
			
		||||
import org.thingsboard.server.common.data.StringUtils;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
public class PerimeterDefinitionSerializer extends JsonSerializer<PerimeterDefinition> {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void serialize(PerimeterDefinition value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
 | 
			
		||||
        if (value instanceof CirclePerimeterDefinition c) {
 | 
			
		||||
            gen.writeStartObject();
 | 
			
		||||
            gen.writeNumberField("latitude",  c.getLatitude());
 | 
			
		||||
            gen.writeNumberField("longitude", c.getLongitude());
 | 
			
		||||
            gen.writeNumberField("radius",    c.getRadius());
 | 
			
		||||
            gen.writeEndObject();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (value instanceof PolygonPerimeterDefinition p) {
 | 
			
		||||
            String raw = p.getPolygonDefinition();
 | 
			
		||||
            if (StringUtils.isBlank(raw)) {
 | 
			
		||||
                throw new IOException("Failed to serialize PolygonPerimeterDefinition with blank: " + value);
 | 
			
		||||
            }
 | 
			
		||||
            ObjectMapper mapper = (ObjectMapper) gen.getCodec();
 | 
			
		||||
            gen.writeTree(mapper.readTree(raw));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        throw new IOException("Failed to serialize PerimeterDefinition from value: " + value);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -20,7 +20,7 @@ import lombok.Data;
 | 
			
		||||
@Data
 | 
			
		||||
public class PolygonPerimeterDefinition implements PerimeterDefinition {
 | 
			
		||||
 | 
			
		||||
    private String polygonsDefinition;
 | 
			
		||||
    private final String polygonDefinition;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public PerimeterType getType() {
 | 
			
		||||
@ -29,7 +29,7 @@ public class PolygonPerimeterDefinition implements PerimeterDefinition {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean checkMatches(Coordinates entityCoordinates) {
 | 
			
		||||
        return GeoUtil.contains(polygonsDefinition, entityCoordinates);
 | 
			
		||||
        return GeoUtil.contains(polygonDefinition, entityCoordinates);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,50 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2025 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.common.util.geo;
 | 
			
		||||
 | 
			
		||||
import org.junit.jupiter.api.Test;
 | 
			
		||||
import org.thingsboard.common.util.JacksonUtil;
 | 
			
		||||
 | 
			
		||||
import static org.assertj.core.api.Assertions.assertThat;
 | 
			
		||||
 | 
			
		||||
public class PerimeterDefinitionDeserializerTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    void shouldDeserializeCircle() {
 | 
			
		||||
        String json = """
 | 
			
		||||
                {"latitude":50.45,"longitude":30.52,"radius":100.0}""";
 | 
			
		||||
 | 
			
		||||
        PerimeterDefinition def = JacksonUtil.fromString(json, PerimeterDefinition.class);
 | 
			
		||||
 | 
			
		||||
        assertThat(def).isNotNull().isInstanceOf(CirclePerimeterDefinition.class);
 | 
			
		||||
 | 
			
		||||
        CirclePerimeterDefinition circle = (CirclePerimeterDefinition) def;
 | 
			
		||||
        assertThat(circle.getLatitude()).isEqualTo(50.45);
 | 
			
		||||
        assertThat(circle.getLongitude()).isEqualTo(30.52);
 | 
			
		||||
        assertThat(circle.getRadius()).isEqualTo(100.0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    void shouldDeserializePolygon() {
 | 
			
		||||
        String json = "[[50.45,30.52],[50.46,30.53],[50.44,30.54]]";
 | 
			
		||||
 | 
			
		||||
        PerimeterDefinition def = JacksonUtil.fromString(json, PerimeterDefinition.class);
 | 
			
		||||
 | 
			
		||||
        assertThat(def).isInstanceOf(PolygonPerimeterDefinition.class);
 | 
			
		||||
        PolygonPerimeterDefinition poly = (PolygonPerimeterDefinition) def;
 | 
			
		||||
        assertThat(poly.getPolygonDefinition()).isEqualTo(json);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,52 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2025 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.common.util.geo;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.databind.JsonNode;
 | 
			
		||||
import org.junit.jupiter.api.Test;
 | 
			
		||||
import org.thingsboard.common.util.JacksonUtil;
 | 
			
		||||
 | 
			
		||||
import static org.assertj.core.api.Assertions.assertThat;
 | 
			
		||||
 | 
			
		||||
public class PerimeterDefinitionSerializerTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    void shouldSerializeCircle() {
 | 
			
		||||
        PerimeterDefinition circle = new CirclePerimeterDefinition(50.45, 30.52, 120.0);
 | 
			
		||||
 | 
			
		||||
        String json = JacksonUtil.writeValueAsString(circle);
 | 
			
		||||
 | 
			
		||||
        JsonNode actual = JacksonUtil.toJsonNode(json);
 | 
			
		||||
        assertThat(actual.get("latitude").asDouble()).isEqualTo(50.45);
 | 
			
		||||
        assertThat(actual.get("longitude").asDouble()).isEqualTo(30.52);
 | 
			
		||||
        assertThat(actual.get("radius").asDouble()).isEqualTo(120.0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    void shouldSerializePolygon() throws Exception {
 | 
			
		||||
        String rawArray = "[[50.45,30.52],[50.46,30.53],[50.44,30.54]]";
 | 
			
		||||
        PerimeterDefinition polygon = new PolygonPerimeterDefinition(rawArray);
 | 
			
		||||
 | 
			
		||||
        String json = JacksonUtil.writeValueAsString(polygon);
 | 
			
		||||
 | 
			
		||||
        JsonNode actual = JacksonUtil.toJsonNode(json);
 | 
			
		||||
        JsonNode expected = JacksonUtil.toJsonNode(rawArray);
 | 
			
		||||
        assertThat(actual).isEqualTo(expected);
 | 
			
		||||
        assertThat(actual.isArray()).isTrue();
 | 
			
		||||
        assertThat(actual.size()).isEqualTo(3);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user