Procházet zdrojové kódy

finished mapping AFC to OGC without filtering

Lukas Cerny před 5 roky
rodič
revize
dcef9a1dec
16 změnil soubory, kde provedl 900 přidání a 376 odebrání
  1. 4 0
      connector-app/src/main/java/io/connector/app/Application.java
  2. 10 2
      connector-core/src/main/java/io/connector/core/http/RequestUriComponent.java
  3. 1 1
      connector-model/src/main/java/io/connector/model/afarcloud/MultiSensor.java
  4. 3 3
      connector-model/src/main/java/io/connector/model/afarcloud/Observation.java
  5. 4 4
      connector-model/src/main/java/io/connector/model/senslog1/UnitData.java
  6. 7 43
      connector-model/src/main/java/io/connector/model/sensorthings/Datastream.java
  7. 25 53
      connector-model/src/main/java/io/connector/model/sensorthings/HistoricalLocation.java
  8. 1 1
      connector-model/src/main/java/io/connector/model/sensorthings/Thing.java
  9. 86 31
      connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/AFCClient.java
  10. 1 1
      connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/AFCConfig.java
  11. 100 0
      connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/Filter.java
  12. 2 2
      connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/UnitCollection.java
  13. 595 174
      connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/gateway/OGCSensorThingsGateway.java
  14. 18 18
      connector-module-ogc-sensorthings/src/main/java/io/connector/module/ogc/sensorthings/gateway/AFarCloudGateway.java
  15. 36 36
      connector-module-senslog1/src/main/java/io/connector/module/senslog1/SensLog1HttpClient.java
  16. 7 7
      connector-module-senslog1/src/main/java/io/connector/module/senslog1/gateway/SensLog1Gateway.java

+ 4 - 0
connector-app/src/main/java/io/connector/app/Application.java

@@ -44,6 +44,7 @@ public class Application extends Thread {
 
     @Override
     public void run() {
+        long startApp = System.currentTimeMillis();
         logger.info("Starting the application {} of the version {}", appConfig.getName(), appConfig.getVersion());
 
         ConfigurationService configService = null;
@@ -76,6 +77,9 @@ public class Application extends Thread {
         Vertx.vertx().deployVerticle(deploy(modules), options, res -> {
             if(res.succeeded()) {
                 logger.info("Deployment id is: {}", res.result());
+                double diffMil = System.currentTimeMillis() - startApp;
+                double sec = diffMil / 1000;
+                logger.info("Started in {} second.", sec);
             } else {
                 logger.error("Deployment failed! The reason is '{}'", res.result());
             }

+ 10 - 2
connector-core/src/main/java/io/connector/core/http/RequestUriComponent.java

@@ -39,10 +39,18 @@ public class RequestUriComponent {
     }
 
     public String getModuleUri() {
-        return domain + create(prefix, module);
+        return domain + getModulePath();
+    }
+
+    public String getModulePath() {
+        return create(prefix, module);
     }
 
     public String getGatewayUri() {
-        return domain + create(prefix, module, gateway);
+        return domain + getGatewayPath();
+    }
+
+    public String getGatewayPath() {
+        return create(prefix, module, gateway);
     }
 }

+ 1 - 1
connector-model/src/main/java/io/connector/model/afarcloud/Unit.java → connector-model/src/main/java/io/connector/model/afarcloud/MultiSensor.java

@@ -2,7 +2,7 @@ package io.connector.model.afarcloud;
 
 import java.util.List;
 
-public class Unit {
+public class MultiSensor {
 
     private String resourceId;
     private String resourceType;

+ 3 - 3
connector-model/src/main/java/io/connector/model/afarcloud/Observation.java

@@ -3,7 +3,7 @@ package io.connector.model.afarcloud;
 public class Observation {
 
     private String time;
-    private Integer altitude;
+    private Double altitude;
     private Double latitude;
     private Double longitude;
     private String geohash;
@@ -23,11 +23,11 @@ public class Observation {
         this.time = time;
     }
 
-    public Integer getAltitude() {
+    public Double getAltitude() {
         return altitude;
     }
 
-    public void setAltitude(Integer altitude) {
+    public void setAltitude(Double altitude) {
         this.altitude = altitude;
     }
 

+ 4 - 4
connector-model/src/main/java/io/connector/model/senslog1/UnitData.java

@@ -116,11 +116,11 @@ public class UnitData {
         return new MessageCodec<UnitData, UnitData>() {
 
             @Override
-            public void encodeToWire(Buffer buffer, UnitData unit) {
+            public void encodeToWire(Buffer buffer, UnitData multiSensor) {
                 JsonObject jsonToEncode = new JsonObject();
-                jsonToEncode.put("unitId", unit.getId());
-                jsonToEncode.put("sensors", Json.encode(unit.getSensors()));
-                jsonToEncode.put("positions", Json.encode(unit.getPositions()));
+                jsonToEncode.put("unitId", multiSensor.getId());
+                jsonToEncode.put("sensors", Json.encode(multiSensor.getSensors()));
+                jsonToEncode.put("positions", Json.encode(multiSensor.getPositions()));
 
                 String jsonToStr = jsonToEncode.encode();
                 int length = jsonToStr.getBytes().length;

+ 7 - 43
connector-model/src/main/java/io/connector/model/sensorthings/Datastream.java

@@ -1,11 +1,7 @@
 package io.connector.model.sensorthings;
 
-import io.vertx.core.json.JsonArray;
 import io.vertx.core.json.JsonObject;
 
-import java.util.ArrayList;
-import java.util.List;
-
 public class Datastream extends JsonObject {
 
     public void setId(String id) {
@@ -72,12 +68,12 @@ public class Datastream extends JsonObject {
         return getString("description");
     }
 
-    public void setUnitOfMeasurement(Unit unitOfMeasurement) {
+    public void setUnitOfMeasurement(UnitOfMeasurement unitOfMeasurement) {
         put("unitOfMeasurement", unitOfMeasurement);
     }
 
-    public Unit getUnitOfMeasurement() {
-        return (Unit)getJsonObject("unitOfMeasurement");
+    public UnitOfMeasurement getUnitOfMeasurement() {
+        return (UnitOfMeasurement)getJsonObject("unitOfMeasurement");
     }
 
     public void setObservationType(String observationType) {
@@ -88,12 +84,12 @@ public class Datastream extends JsonObject {
         return getString("observationType");
     }
 
-    public void setObservedArea(Area observedArea) {
+    public void setObservedArea(Geometry observedArea) {
         put("observedArea", observedArea);
     }
 
-    public Area getObservedArea() {
-        return (Area)getJsonObject("observedArea");
+    public Geometry getObservedArea() {
+        return (Geometry)getJsonObject("observedArea");
     }
 
     public void setPhenomenonTime(String phenomenonTime) {
@@ -112,40 +108,8 @@ public class Datastream extends JsonObject {
         return getString("resultTime");
     }
 
-    public static class Area extends JsonObject {
-
-        public void setType(String type) {
-            put("type", type);
-        }
-
-        public String getType() {
-            return getString("type");
-        }
-
-        public void setCoordinates(List<List<Integer>> coordinates) {
-            JsonArray jsonCoordinates = new JsonArray();
-            for (List<Integer> coordinate : coordinates) {
-                jsonCoordinates.add(new JsonArray(coordinate));
-            }
-            put("coordinates", jsonCoordinates);
-        }
-
-        public List<List<Integer>> getCoordinates() {
-            JsonArray jsonCoordinates = getJsonArray("coordinates");
-            List<List<Integer>> coordinates = new ArrayList<>(jsonCoordinates.size());
-            for (int c = 0; c < jsonCoordinates.size(); c++) {
-                JsonArray jsonCoordinate = jsonCoordinates.getJsonArray(c);
-                List<Integer> coordinate = new ArrayList<>(jsonCoordinate.size());
-                for (int i = 0; i < jsonCoordinate.size(); i++) {
-                    coordinate.add(jsonCoordinate.getInteger(i));
-                }
-                coordinates.add(coordinate);
-            }
-            return coordinates;
-        }
-    }
 
-    public static class Unit extends JsonObject {
+    public static class UnitOfMeasurement extends JsonObject {
 
         public void setName(String name) {
             put("name", name);

+ 25 - 53
connector-model/src/main/java/io/connector/model/sensorthings/HistoricalLocation.java

@@ -1,74 +1,46 @@
 package io.connector.model.sensorthings;
 
-import io.vertx.core.json.JsonArray;
 import io.vertx.core.json.JsonObject;
 
-import java.util.ArrayList;
-import java.util.List;
-
 public class HistoricalLocation extends JsonObject {
 
-    public void setValue(List<Value> values) {
-        put("value", new JsonArray(values));
+    public void setId(String id) {
+        put("@iot.id", id);
     }
 
-    public List<Value> getValue() {
-        JsonArray jsonValue = getJsonArray("value");
-        List<Value> value = new ArrayList<>(jsonValue.size());
-        for (int i = 0; i < jsonValue.size(); i++) {
-            value.add((Value)jsonValue.getJsonObject(i));
-        }
-        return value;
+    public String getId() {
+        return getString("@iot.id");
     }
 
-    public void setNextLink(String nextLink) {
-        put("@iot.nextLink", nextLink);
+    public void setSelfLink(String selfLink) {
+        put("@iot.selfLink", selfLink);
     }
 
-    public String getNextLink() {
-        return getString("@iot.nextLink");
+    public String getSelfLink() {
+        return getString("@iot.selfLink");
     }
 
-    public static class Value extends JsonObject {
-
-        public void setId(String id) {
-            put("@iot.id", id);
-        }
-
-        public String getId() {
-            return getString("@iot.id");
-        }
-
-        public void setSelfLink(String selfLink) {
-            put("@iot.selfLink", selfLink);
-        }
-
-        public String getSelfLink() {
-            return getString("@iot.selfLink");
-        }
-
-        public void setLocationsNavigationLink(String locationNavigationLink) {
-            put("Locations@iot.navigationLink", locationNavigationLink);
-        }
+    public void setLocationsNavigationLink(String locationNavigationLink) {
+        put("Locations@iot.navigationLink", locationNavigationLink);
+    }
 
-        public String getLocationsNavigationLink() {
-            return getString("Locations@iot.navigationLink");
-        }
+    public String getLocationsNavigationLink() {
+        return getString("Locations@iot.navigationLink");
+    }
 
-        public void setThingNavigationLink(String thingsNavigationLink) {
-            put("Thing@iot.navigationLink", thingsNavigationLink);
-        }
+    public void setThingNavigationLink(String thingsNavigationLink) {
+        put("Thing@iot.navigationLink", thingsNavigationLink);
+    }
 
-        public String getThingNavigationLink() {
-            return getString("Thing@iot.navigationLink");
-        }
+    public String getThingNavigationLink() {
+        return getString("Thing@iot.navigationLink");
+    }
 
-        public void setTime(String time) {
-            put("time", time);
-        }
+    public void setTime(String time) {
+        put("time", time);
+    }
 
-        public String getTime() {
-            return getString("time");
-        }
+    public String getTime() {
+        return getString("time");
     }
 }

+ 1 - 1
connector-model/src/main/java/io/connector/model/sensorthings/Think.java → connector-model/src/main/java/io/connector/model/sensorthings/Thing.java

@@ -3,7 +3,7 @@ package io.connector.model.sensorthings;
 import io.vertx.core.json.JsonObject;
 
 
-public class Think extends JsonObject {
+public class Thing extends JsonObject {
 
     public void setId(String id) {
         put("@iot.id", id);

+ 86 - 31
connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/AFCClient.java

@@ -6,9 +6,11 @@ import cz.senslog.common.http.HttpRequest;
 import cz.senslog.common.http.HttpResponse;
 import cz.senslog.common.http.URLBuilder;
 import io.connector.core.config.HostConfig;
+import io.connector.model.afarcloud.MultiSensor;
 import io.connector.model.afarcloud.MultiSimpleObservation;
-import io.connector.model.afarcloud.Unit;
+import io.connector.model.afarcloud.ResourceMeasurement;
 import io.connector.module.afarcloud.gateway.SensLog1Gateway;
+import io.vertx.core.json.JsonArray;
 import io.vertx.core.json.JsonObject;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
@@ -46,14 +48,26 @@ public class AFCClient {
         logger.info("Converted {} observations.", observations.size());
     }
 
-    public List<Unit> getAllSensors() {
+    public List<ResourceMeasurement> getObservationsBySensor(Filter filter) {
+        HostConfig host = new HostConfig(config.getRetrievalDomain(), "getObservationsBySensor/latest");
 
-        HostConfig host = new HostConfig(config.getInfoDomain(), "registry/getAllSensors");
+        URLBuilder urlBuilder = URLBuilder.newBuilder(host.getDomain(), host.getPath());
+
+        if (filter.startTime != null) {
+            urlBuilder.addParam("start_time", filter.startTime());
+        }
+        if (filter.endTime != null) {
+            urlBuilder.addParam("end_time", filter.endTime());
+        }
+        if (filter.entityNames != null) {
+            urlBuilder.addParam("entityNames", filter.entityNames());
+        }
+        if (filter.measurements != null) {
+            urlBuilder.addParam("measurements", filter.measurements());
+        }
 
         HttpRequest request = HttpRequest.newBuilder()
-                .contentType(TEXT_PLAIN)
-                .url(URLBuilder.newBuilder(host.getDomain(), host.getPath()).build())
-                .GET().build();
+                .contentType(TEXT_PLAIN).url(urlBuilder.build()).GET().build();
 
         HttpResponse response = httpClient.send(request);
 
@@ -61,14 +75,50 @@ public class AFCClient {
             throw new RuntimeException(response.getBody());
         }
 
-        final Type unitType = new TypeToken<Collection<Unit>>() {}.getType();
-        return jsonToObject(response.getBody(), unitType);
+        JsonObject jsonObject = new JsonObject(response.getBody());
+        JsonObject results = jsonObject.getJsonObject("results");
+        JsonArray resources = results.getJsonArray("resources");
+
+        final Type measurementType = new TypeToken<Collection<ResourceMeasurement>>() {}.getType();
+        return jsonToObject(resources.encode(), measurementType);
+    }
+
+    public List<ResourceMeasurement> getLatestObservationsBySensor(Filter filter) {
+
+        HostConfig host = new HostConfig(config.getRetrievalDomain(), "getObservationsBySensor/latest");
+
+        URLBuilder urlBuilder = URLBuilder.newBuilder(host.getDomain(), host.getPath());
+
+        if (filter.limit > 0) {
+            urlBuilder.addParam("limit", filter.limit());
+        }
+        if (filter.entityNames != null) {
+            urlBuilder.addParam("entityNames", filter.entityNames());
+        }
+        if (filter.measurements != null) {
+            urlBuilder.addParam("measurements", filter.measurements());
+        }
+
+        HttpRequest request = HttpRequest.newBuilder()
+                .contentType(TEXT_PLAIN).url(urlBuilder.build()).GET().build();
 
+        HttpResponse response = httpClient.send(request);
+
+        if (response.isError()) {
+            throw new RuntimeException(response.getBody());
+        }
+
+        JsonObject jsonObject = new JsonObject(response.getBody());
+        JsonObject results = jsonObject.getJsonObject("results");
+        JsonArray resources = results.getJsonArray("resources");
+
+        final Type measurementType = new TypeToken<Collection<ResourceMeasurement>>() {}.getType();
+        return jsonToObject(resources.encode(), measurementType);
     }
 
-    public Unit getSensor(String id) {
+    public List<MultiSensor> getAllMultiSensors() {
 
-        HostConfig host = new HostConfig(config.getInfoDomain(), format("registry/getSensor/%s", id));
+        HostConfig host = new HostConfig(config.getInfoDomain(), "registry/getAllSensors");
 
         HttpRequest request = HttpRequest.newBuilder()
                 .contentType(TEXT_PLAIN)
@@ -81,32 +131,37 @@ public class AFCClient {
             throw new RuntimeException(response.getBody());
         }
 
-        return jsonToObject(response.getBody(), Unit.class);
+        final Type unitType = new TypeToken<Collection<MultiSensor>>() {}.getType();
+        return jsonToObject(response.getBody(), unitType);
 
-        /*
+    }
 
+    public MultiSensor getSensorByResourceId(String resourceId) {
+        HostConfig host = new HostConfig(config.getInfoDomain(), format("registry/getSensor/%s", resourceId));
 
-        Unit unit = new Unit();
-        unit.setResourceId(id);
-        unit.setResourceType("AFC 8CF95740000008AE");
-        unit.setResourceUrn("urn:afc:AS07:enviro:IMA:air_quality:10002222");
-        unit.setLatitude(50.0382589);
-        unit.setLongitude(14.6112164);
-        unit.setAltitude(280.0);
-        unit.setPreprocessing(false);
-        unit.setPythonScript("");
+        HttpRequest request = HttpRequest.newBuilder()
+                .contentType(TEXT_PLAIN)
+                .url(URLBuilder.newBuilder(host.getDomain(), host.getPath()).build())
+                .GET().build();
+
+        HttpResponse response = httpClient.send(request);
 
-        Unit.SensorSchema sensorSchema = new Unit.SensorSchema();
-        sensorSchema.setObservedProperty("air_temperature");
-        sensorSchema.setUom("http://qudt.org/vocab/unit/DEG_C");
-        sensorSchema.setAccuracy(0.1);
-        sensorSchema.setPropertyId(456);
-        sensorSchema.setMin_value(-40.0);
-        sensorSchema.setMax_value(60.0);
-        unit.setObservations(singletonList(sensorSchema));
+        if (response.isError()) {
+            throw new RuntimeException(response.getBody());
+        }
 
-        return unit;
+        return jsonToObject(response.getBody(), MultiSensor.class);
+    }
 
-         */
+    public MultiSensor getSensorByResourceUrn(String resourceUrn) {
+        List<MultiSensor> multiSensors = getAllMultiSensors();
+        for (MultiSensor multiSensor : multiSensors) {
+            if (multiSensor.getResourceUrn().equals(resourceUrn)) {
+                return multiSensor;
+            }
+        }
+        throw new IllegalArgumentException(
+                "Can not find sensor by resourceUrn '" + resourceUrn + "'."
+        );
     }
 }

+ 1 - 1
connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/AFCConfig.java

@@ -10,7 +10,7 @@ public class AFCConfig {
 
     AFCConfig(DefaultConfig defaultConfig) {
         this.telemetryDomain = defaultConfig.getPropertyConfig("telemetryApi").getStringProperty("domain");
-        this.retrievalDomain = defaultConfig.getPropertyConfig("telemetryApi").getStringProperty("domain");
+        this.retrievalDomain = defaultConfig.getPropertyConfig("retrievalApi").getStringProperty("domain");
         this.infoDomain = defaultConfig.getPropertyConfig("infoApi").getStringProperty("domain");
 
     }

+ 100 - 0
connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/Filter.java

@@ -0,0 +1,100 @@
+package io.connector.module.afarcloud;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class Filter {
+    protected int limit = 1;
+    protected List<String> entityNames;
+    protected List<String> devices;
+    protected List<String> services;
+    protected List<String> types;
+    protected List<String> providers;
+    protected List<String> measurements;
+    protected String order;
+    protected Integer minAltitude;
+    protected Double centrLong;
+    protected Double centrLat;
+    protected Integer radius;
+
+    protected String startTime;
+    protected String endTime;
+
+    public Filter limit(int limit) {
+        this.limit = limit;
+        return this;
+    }
+
+    public int limit() {
+        return limit;
+    }
+
+    public Filter entityNames(String... entityNames) {
+        this.entityNames = Arrays.asList(entityNames);
+        return this;
+    }
+
+    public String entityNames() {
+        if (entityNames == null) {
+            return "";
+        }
+
+        return String.join(",", entityNames);
+    }
+
+    public Filter devices(String... devices) {
+        this.devices = Arrays.asList(devices);
+        return this;
+    }
+
+    public Filter services(String... services) {
+        this.services = Arrays.asList(services);
+        return this;
+    }
+
+    public Filter types(String... types) {
+        this.types = Arrays.asList(types);
+        return this;
+    }
+
+    public Filter provides(String... provides) {
+        this.providers = Arrays.asList(provides);
+        return this;
+    }
+
+    public Filter measurements(String... measurements) {
+        this.measurements = Arrays.asList(measurements);
+        return this;
+    }
+
+    public String measurements() {
+        if (measurements == null) {
+            return "";
+        }
+
+        return String.join(",", measurements);
+    }
+
+    public Filter order(String order) {
+        this.order = order;
+        return this;
+    }
+
+    public Filter startTime(String startTime) {
+        this.startTime = startTime;
+        return this;
+    }
+
+    public String startTime() {
+        return this.startTime;
+    }
+
+    public Filter endTime(String endTime) {
+        this.endTime = endTime;
+        return this;
+    }
+
+    public String endTime() {
+        return this.endTime;
+    }
+}

+ 2 - 2
connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/UnitCollection.java

@@ -12,7 +12,7 @@ public class UnitCollection extends JsonArray {
         }
     }
 
-    public void addUnit(Unit unit) {
-        this.add(unit);
+    public void addUnit(Unit multiSensor) {
+        this.add(multiSensor);
     }
 }

+ 595 - 174
connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/gateway/OGCSensorThingsGateway.java

@@ -2,20 +2,30 @@ package io.connector.module.afarcloud.gateway;
 
 import io.connector.core.AbstractGateway;
 import io.connector.core.http.RequestUriComponent;
-import io.connector.model.afarcloud.Unit;
+import io.connector.model.afarcloud.MultiSensor;
+import io.connector.model.afarcloud.ResourceMeasurement;
+import io.connector.model.afarcloud.SensorTelemetry;
 import io.connector.model.sensorthings.*;
 import io.connector.module.afarcloud.AFCClient;
-import io.vertx.core.json.JsonObject;
+import io.connector.module.afarcloud.Filter;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.util.*;
+import java.util.function.Supplier;
 
 import static cz.senslog.common.http.HttpContentType.APPLICATION_JSON;
 import static cz.senslog.common.http.HttpHeader.CONTENT_TYPE;
 import static io.connector.core.AddressPath.Creator.create;
 import static io.vertx.core.json.Json.encode;
+import static java.lang.Math.sqrt;
+import static java.lang.StrictMath.pow;
 import static java.lang.String.format;
+import static java.time.format.DateTimeFormatter.ofPattern;
 import static java.util.Arrays.asList;
+import static java.util.Optional.ofNullable;
 
 public class OGCSensorThingsGateway extends AbstractGateway {
 
@@ -29,236 +39,647 @@ public class OGCSensorThingsGateway extends AbstractGateway {
     @Override
     protected void run() {
 
-        router().get(create("Things(:id)")).handler(ctx -> {
+        router().get(create("Things")).handler(ctx -> {
             RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
-            String id = ctx.pathParam("id");
+            List<MultiSensor> afcMultiSensors = client.getAllMultiSensors();
+            List<Thing> ogcThings = Converter.convertToThing(afcMultiSensors, uriComponent);
+            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(encode(ogcThings));
+        });
 
-            Think think = new Think();
-            think.setId(id);
-            think.setSelfLink(format("%s/Things(%s)", uriComponent.getGatewayUri(), id));
-            think.setLocationsNavigationLink(format("Things(%s)/Locations", id));
-            think.setDataStreamNavigationLink(format("Things(%s)/Datastreams", id));
-            think.setHistoricalLocationsNavigationLink(format("Things(%s)/HistoricalLocations", id));
-            think.setName("Oven");
-            think.setDescription("This thing is an oven.");
-            think.setProperties(new JsonObject()
-                    .put("owner", "Noah Liang")
-                    .put("color", "Black")
-            );
+        router().get(create("Things(:id)")).handler(ctx -> {
+            RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
+            String resourceUrn = ctx.pathParam("id"); // resourceUrn
+            MultiSensor afcMultiSensor = client.getSensorByResourceUrn(resourceUrn);
+            Thing ogcThing = Converter.convertToThing(afcMultiSensor, uriComponent);
+            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(ogcThing.encode());
+        });
 
-            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(think.encode());
+        router().get(create("Datastreams(:id)/Thing")).handler(ctx -> {
+            RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
+            String id = ctx.pathParam("id"); // resourceUrn + observedProperty
+            String[] idCmp = Converter.disassemblyId(id);
+            String resourceUrn = idCmp[0];
+            ctx.reroute(format("%s/Things(%s)", uriComponent.getGatewayPath(), resourceUrn));
         });
 
-        router().get(create("Locations(:id)")).handler(ctx -> {
+        router().get(create("HistoricalLocations(:id)/Thing")).handler(ctx -> {
             RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
-            String id = ctx.pathParam("id");
+            String id = ctx.pathParam("id"); // resourceUrn + observedProperty + time
 
-            Unit afcUnit = client.getSensor(id);
+            String [] idCmp = Converter.disassemblyId(id);
+            String resourceUrn = idCmp[0];
 
-            Location ogcLocation = new Location();
-            ogcLocation.setId(id);
-            ogcLocation.setSelfLink(format("%s/Locations(%s)", uriComponent.getGatewayUri(), id));
-            ogcLocation.setHistoricalLocationsNavigationLink("unknown");
-            ogcLocation.setName(afcUnit.getResourceType());
-            ogcLocation.setDescription(afcUnit.getResourceUrn());
-            ogcLocation.setEncodingType("application/vnd.geo+json");
+            MultiSensor afcMultiSensor = client.getSensorByResourceUrn(resourceUrn);
+            Thing ogcThing = Converter.convertToThing(afcMultiSensor, uriComponent);
+            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(ogcThing.encode());
+        });
 
-            Location.Info info = new Location.Info();
-            info.setType("Feature");
-            ogcLocation.setLocation(info);
+        router().get(create("Datastreams(:id)")).handler(ctx -> {
+            RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
+            String id = ctx.pathParam("id"); // resourceUrn + observedProperty
+            String[] idCmp = Converter.disassemblyId(id);
+            String resourceUrn = idCmp[0], observedProperty = idCmp[1];
+            MultiSensor afcMultiSensor = client.getSensorByResourceUrn(resourceUrn);
+            List<ResourceMeasurement> afcMeasurements = client.getLatestObservationsBySensor(new Filter()
+                    .limit(1)
+                    .entityNames(afcMultiSensor.getResourceId())
+                    .measurements(observedProperty)
+            );
+            final Supplier<IllegalArgumentException> exception = () -> new IllegalArgumentException("Can not find Datastream with @iot.id \"" + id + "\".");
 
-            Geometry geometry = new Geometry();
-            geometry.setType("Point");
-            geometry.setCoordinates(asList(afcUnit.getLatitude(), afcUnit.getLongitude()));
-            info.setGeometry(geometry);
+            Optional<MultiSensor.SensorSchema> afcSensorOpt = afcMultiSensor.getObservations().stream().filter(s -> s.getObservedProperty().equals(observedProperty)).findFirst();
+            MultiSensor.SensorSchema afcSensor = afcSensorOpt.orElseThrow(exception);
 
-            /*
-            Location example = new Location();
-            example.setId(Integer.parseInt(id));
-            example.setSelfLink(format("%s/Locations(%s)", uriComponent.getGatewayUri(), id));
-            example.setHistoricalLocationsNavigationLink(format("Locations(%s)/HistoricalLocations", id));
-            example.setEncodingType("application/vnd.geo+json");
+            Optional<ResourceMeasurement> afcMeasurementOpt = ofNullable(afcMeasurements.size() == 1 ? afcMeasurements.get(0) : null);
+            ResourceMeasurement afcMeasurement = afcMeasurementOpt.orElseThrow(exception);
 
-            example.setName("CCIT");
-            example.setDescription("Calgary Center for Innvative Technologies");
+            Optional<SensorTelemetry> afcTelemetryOpt = ofNullable(afcMeasurement.getMeasurements().size() == 1 ? afcMeasurement.getMeasurements().get(0) : null);
+            SensorTelemetry afcTelemetry = afcTelemetryOpt.orElseThrow(exception);
 
-            Location.Info exampleInfo = new Location.Info();
-            exampleInfo.setType("Feature");
-            example.setLocation(exampleInfo);
+            Datastream datastream = Converter.convertToDataStream(afcMultiSensor, afcSensor, afcTelemetry, uriComponent);
+            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(datastream.encode());
+        });
 
-            Geometry exampleGeometry = new Geometry();
-            exampleGeometry.setType("Point");
-            exampleGeometry.setCoordinates(asList(-114.06,51.05));
-            exampleInfo.setGeometry(exampleGeometry);
-            */
+        router().get(create("Things(:id)/Datastreams")).handler(ctx -> {
+            RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
+            String resourceUrn = ctx.pathParam("id"); // resourceUrn
+            MultiSensor afcMultiSensor = client.getSensorByResourceUrn(resourceUrn);
+            List<ResourceMeasurement> afcMeasurements = client.getLatestObservationsBySensor(new Filter()
+                    .limit(1)
+                    .entityNames(afcMultiSensor.getResourceId())
+            );
+            ResourceMeasurement afcMeasurement = afcMeasurements.size() == 1 ? afcMeasurements.get(0) : null;
+            List<Datastream> ogcDataStream = Converter.convertToDataStream(afcMultiSensor, afcMeasurement, uriComponent);
+            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(encode(ogcDataStream));
+        });
 
-            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(ogcLocation.encode());
+        router().get(create("Sensors(:id)/Datastreams")).handler(ctx -> {
+            RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
+            String resourceUrn = ctx.pathParam("id"); // resourceUrn
+            ctx.reroute(format("%s/Things(%s)/Datastreams", uriComponent.getGatewayPath(), resourceUrn));
         });
 
-        router().get(create("Datastreams(:id)")).handler(ctx -> {
+        router().get(create("ObservedProperties(:id)/Datastream")).handler(ctx -> {
             RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
-            String id = ctx.pathParam("id");
+            String id = ctx.pathParam("id"); // resourceUrn + observedProperty
+            ctx.reroute(format("%s/Datastreams(%s)", uriComponent.getGatewayPath(), id));
+        });
 
-            Datastream datastream = new Datastream();
-            datastream.setId(id);
-            datastream.setSelfLink(format("%s/Datastreams(%s)", uriComponent.getGatewayUri(), id));
-            datastream.setThingNavigationLink(format("HistoricalLocations(%s)/Thing", id));
-            datastream.setSensorNavigationLink(format("Datastreams(%s)/Sensor", id));
-            datastream.setObservedPropertyNavigationLink(format("Datastreams(%s)/ObservedProperty", id));
-            datastream.setObservationsNavigationLink(format("Datastreams(%s)/Observations", id));
-            datastream.setName("oven temperature");
-            datastream.setDescription("This is a datastream measuring the air temperature in an oven.");
-
-            Datastream.Unit unit = new Datastream.Unit();
-            unit.setName("degree Celsius");
-            unit.setSymbol("\u00B0" + "C");
-            unit.setDefinition("http://unitsofmeasure.org/ucum.html#para-30");
-            datastream.setUnitOfMeasurement(unit);
+        router().get(create("Observations(:id)/Datastream")).handler(ctx -> {
+            RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
+            String id = ctx.pathParam("id"); // resource + measurement + time
+            String[] idCmp = Converter.disassemblyId(id);
+            String resource = idCmp[0], measurement = idCmp[1];
+            MultiSensor afcMultiSensor = client.getSensorByResourceId(resource);
+            String resourceUrn = afcMultiSensor.getResourceUrn();
+            ctx.reroute(format("%s/Datastreams(%s)", uriComponent.getGatewayPath(), Converter.assemblyId(resourceUrn, measurement)));
+        });
 
-            datastream.setObservationType("http://www.opengis.net/def/observationType/OGC-OM/2.0/OM_Measurement");
+        router().get(create("Sensors(:id)")).handler(ctx -> {
+            RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
+            String resourceUrn = ctx.pathParam("id"); // resourceUrn
+            MultiSensor afcMultiSensor = client.getSensorByResourceUrn(resourceUrn);
+            Sensor ogcSensor = Converter.convertToSensor(afcMultiSensor, uriComponent);
+            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(ogcSensor.encode());
+        });
 
-            Datastream.Area area = new Datastream.Area();
-            area.setType("Polygon");
-            area.setCoordinates(asList(
-                    asList(100, 0),
-                    asList(101, 0),
-                    asList(101, 1),
-                    asList(100, 1),
-                    asList(100, 0)
-            ));
-            datastream.setObservedArea(area);
+        router().get(create("Datastreams(:id)/Sensor")).handler(ctx -> {
+            RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
+            String id = ctx.pathParam("id"); // resourceUrn + observedProperty
+            String[] idCmp = Converter.disassemblyId(id);
+            String resourceUrn = idCmp[0];
+            ctx.reroute(format("%s/Sensors(%s)", uriComponent.getGatewayPath(), resourceUrn));
+        });
 
-            datastream.setPhenomenonTime("2014-03-01T13:00:00Z/2015-05-11T15:30:00Z");
-            datastream.setResultTime("2014-03-01T13:00:00Z/2015-05-11T15:30:00Z");
+        router().get(create("ObservedProperties(:id)")).handler(ctx -> {
+            RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
+            String id = ctx.pathParam("id"); // resourceUrn + observedProperty
+            String[] idCmp = Converter.disassemblyId(id);
+            String resourceUrn = idCmp[0], observedProperty = idCmp[1];
+            final Supplier<IllegalArgumentException> exception = () -> new IllegalArgumentException("Can not find Datastream with @iot.id \"" + id + "\".");
+            MultiSensor afcMultiSensor = client.getSensorByResourceUrn(resourceUrn);
+            Optional<MultiSensor.SensorSchema> afcSensorOpt = afcMultiSensor.getObservations().stream().filter(s -> s.getObservedProperty().equals(observedProperty)).findFirst();
+            MultiSensor.SensorSchema afcSensor = afcSensorOpt.orElseThrow(exception);
+            ObservedProperty property = Converter.convertToObservedProperty(afcMultiSensor, afcSensor, uriComponent);
+            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(property.encode());
+        });
 
-            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(datastream.encode());
+        router().get(create("Datastreams(:id)/ObservedProperty")).handler(ctx -> {
+            RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
+            String id = ctx.pathParam("id"); // resourceUrn + observedProperty
+            ctx.reroute(format("%s/ObservedProperties(%s)", uriComponent.getGatewayPath(), id));
         });
 
-        router().get(create("HistoricalLocations(:id)")).handler(ctx -> {
+        router().get(create("Observations(:id)")).handler(ctx -> {
             RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
-            int id = Integer.parseInt(ctx.pathParam("id"));
+            String id = ctx.pathParam("id"); // resource + measurement + time
+            String[] idCmp = Converter.disassemblyId(id);
+            String resource = idCmp[0], measurement = idCmp[1];
 
-            HistoricalLocation historicalLocation = new HistoricalLocation();
+            List<ResourceMeasurement> measurements = client.getLatestObservationsBySensor(new Filter()
+                    .limit(1).entityNames(resource).measurements(measurement)
+            );
+            final Supplier<IllegalArgumentException> exception = () -> new IllegalArgumentException("Can not find Datastream with @iot.id \"" + id + "\".");
 
-            List<HistoricalLocation.Value> valueList = new ArrayList<>();
-            for (int valueId = id; valueId < id + 2; valueId++) {
-                HistoricalLocation.Value value = new HistoricalLocation.Value();
-                value.setId(String.valueOf(valueId));
-                value.setSelfLink(format("%s/HistoricalLocations(%s)", uriComponent.getGatewayUri(), valueId));
-                value.setLocationsNavigationLink(format("HistoricalLocations(%s)/Locations", valueId));
-                value.setThingNavigationLink(format("HistoricalLocations(%s)/Thing", valueId));
-                value.setTime("2015-01-25T12:00:00-07:00");
-                valueList.add(value);
-            }
-            historicalLocation.setValue(valueList);
-            historicalLocation.setNextLink(format("%s/Things(%s)/HistoricalLocations?$skip=%s&top=%s", uriComponent.getDomain(), id, valueList.size(), valueList.size()));
+            Optional<ResourceMeasurement> measurementOpt = ofNullable(measurements.size() == 1 ? measurements.get(0) : null);
+            ResourceMeasurement afcMeasurement = measurementOpt.orElseThrow(exception);
+
+            Optional<SensorTelemetry> telemetryOpt = ofNullable(afcMeasurement.getMeasurements().size() == 1 ? afcMeasurement.getMeasurements().get(0) : null);
+            SensorTelemetry afcTelemetry = telemetryOpt.orElseThrow(exception);
+
+            Optional<io.connector.model.afarcloud.Observation> observationOpt = ofNullable(afcTelemetry.getObservations().size() == 1 ? afcTelemetry.getObservations().get(0) : null);
+            io.connector.model.afarcloud.Observation afcObservation = observationOpt.orElseThrow(exception);
 
-            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(historicalLocation.encode());
+            Observation ogcObservation = Converter.convertToObservation(afcMeasurement, afcTelemetry, afcObservation, uriComponent);
+            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(ogcObservation.encode());
         });
 
-        router().get(create("Sensors")).handler(ctx -> {
+        // without filter
+        router().get(create("Datastreams(:id)/Observations")).handler(ctx -> {
             RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
-            List<Unit> afcAllSensors = client.getAllSensors();
-            List<Sensor> ogcSensors = Converter.convertSensors(afcAllSensors, uriComponent);
-            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(encode(ogcSensors));
+            String id = ctx.pathParam("id"); // resourceUrn + observedProperty
+            String filter = ctx.request().getParam("filter"); // TODO filter
+
+            String [] idCmp = Converter.disassemblyId(id);
+            String resourceUrn = idCmp[0], observedProperty = idCmp[1];
+
+            MultiSensor afcMultiSensor = client.getSensorByResourceUrn(resourceUrn);
+            List<ResourceMeasurement> measurements = client.getLatestObservationsBySensor(new Filter()
+                    .limit(1).entityNames(afcMultiSensor.getResourceId()).measurements(observedProperty)
+            );
+            final Supplier<IllegalArgumentException> exception = () -> new IllegalArgumentException("Can not find Datastream with @iot.id \"" + id + "\".");
+
+            Optional<ResourceMeasurement> measurementOpt = ofNullable(measurements.size() == 1 ? measurements.get(0) : null);
+            ResourceMeasurement afcMeasurement = measurementOpt.orElseThrow(exception);
+
+            Optional<SensorTelemetry> telemetryOpt = ofNullable(afcMeasurement.getMeasurements().size() == 1 ? afcMeasurement.getMeasurements().get(0) : null);
+            SensorTelemetry afcTelemetry = telemetryOpt.orElseThrow(exception);
+
+            Optional<io.connector.model.afarcloud.Observation> observationOpt = ofNullable(afcTelemetry.getObservations().size() == 1 ? afcTelemetry.getObservations().get(0) : null);
+            io.connector.model.afarcloud.Observation afcObservation = observationOpt.orElseThrow(exception);
+
+            Observation ogcObservation = Converter.convertToObservation(afcMeasurement, afcTelemetry, afcObservation, uriComponent);
+            List<Observation> ogcObservations = Collections.singletonList(ogcObservation);
+            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(encode(ogcObservations));
         });
 
-        router().get(create("Sensors(:id)")).handler(ctx -> {
+        router().get(create("FeaturesOfInterest(:id)")).handler(ctx -> {
             RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
             String id = ctx.pathParam("id");
-            Unit afcUnit = client.getSensor(id);
-            Sensor ogcSensor = Converter.convertSensor(afcUnit, uriComponent);
-
-            /*
-            Sensor example = new Sensor();
-            example.setId(Integer.parseInt(id));
-            example.setSelfLink(format("%s/Sensors(%s)", uriComponent.getGatewayUri(), id));
-            example.setDataStreamNavigationLink(format("Sensors(%s)/Datastreams", id));
-            example.setName("TMP36");
-            example.setDescription("TMP36 - Analog Temperature sensor");
-            example.setEncodingType("application/pdf");
-            example.setMetadata("http://example.org/TMP35_36_37.pdf");
-            */
+            FeatureOfInterest featureOfInterest = new FeatureOfInterest();
+            featureOfInterest.setId(id);
+            featureOfInterest.setSelfLink("https://storage07-afarcloud.qa.pdmfc.com/storage/rest/registry/getAllObservationTypes");
 
-            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(ogcSensor.encode());
+            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(featureOfInterest.encode());
         });
 
-        router().get(create("ObservedProperties(:id)")).handler(ctx -> {
+        router().get(create("Locations(:id)")).handler(ctx -> {
             RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
-            String id = ctx.pathParam("id");
+            String id = ctx.pathParam("id"); // resourceUrn + observedProperty + time
+            String [] idCmp = Converter.disassemblyId(id);
+            String resourceUrn = idCmp[0], observedProperty = idCmp[1], time = idCmp[2];
+            final DateTimeFormatter formatter = new DateTimeFormatterBuilder().append(ofPattern("yyyy-MM-dd HH:mm"))
+                    .toFormatter().withZone(ZoneId.of("UTC"));
+
+            Instant startTime = Instant.parse(time);
+            Instant endTime = startTime.plusSeconds(60);
+
+            MultiSensor afcMultiSensor = client.getSensorByResourceUrn(resourceUrn);
+            List<ResourceMeasurement> afcMeasurements = client.getObservationsBySensor(new Filter()
+                    .startTime(formatter.format(startTime)).endTime(formatter.format(endTime))
+                    .entityNames(afcMultiSensor.getResourceId()).measurements(observedProperty)
+            );
+            final Supplier<IllegalArgumentException> exception = () -> new IllegalArgumentException("Can not find Location with @iot.id \"" + resourceUrn + "\".");
 
-            ObservedProperty observedProperty = new ObservedProperty();
-            observedProperty.setId(id);
-            observedProperty.setSelfLink(format("%s/ObservedProperties(%s)", uriComponent.getGatewayUri(), id));
-            observedProperty.setDataStreamNavigationLink(format("ObservedProperties(%s)/Datastreams", id));
-            observedProperty.setDescription("The dewpoint temperature is the temperature to which the air must be cooled, at constant pressure, for dew to form. As the grass and other objects near the ground cool to the dewpoint, some of the water vapor in the atmosphere condenses into liquid water on the objects.");
-            observedProperty.setName("DewPoint Temperature");
-            observedProperty.setDefinition("http://dbpedia.org/page/Dew_point");
-
-            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(observedProperty.encode());
+            Optional<ResourceMeasurement> afcMeasurementOpt = ofNullable(afcMeasurements.size() == 1 ? afcMeasurements.get(0) : null);
+            ResourceMeasurement afcMeasurement = afcMeasurementOpt.orElseThrow(exception);
+
+            AFCLocationList afcLocations = AFCLocationUtils.sort(afcMeasurement);
+            Optional<AFCAggrLocation> afcLastLocationOpt = ofNullable(afcLocations.getLast());
+            AFCAggrLocation afcLastLocation = afcLastLocationOpt.orElseThrow(exception);
+
+            Location ogcLocation = Converter.convertToLocation(afcMultiSensor, afcLastLocation, uriComponent);
+            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(ogcLocation.encode());
         });
 
-        router().get(create("Observations(:id)")).handler(ctx -> {
+        router().get(create("Things(:id)/Locations")).handler(ctx -> {
             RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
-            String id = ctx.pathParam("id");
+            String resourceUrn = ctx.pathParam("id");  // resourceUrn
 
-            Observation observation = new Observation();
-            observation.setId(id);
-            observation.setSelfLink(format("%s/Observations(%s)", uriComponent.getGatewayUri(), id));
-            observation.setFeatureOfInterestNavigationLink(format("Observations(%s)/FeatureOfInterest", id));
-            observation.setDataStreamNavigationLink(format("Observations(%s)/Datastream", id));
-            observation.setPhenomenonTime("2014-12-31T11:59:59.00+08:00");
-            observation.setResultTime("2014-12-31T11:59:59.00+08:00");
-            observation.setResult(70.4);
-
-            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(observation.encode());
+            MultiSensor afcMultiSensor = client.getSensorByResourceUrn(resourceUrn);
+            List<ResourceMeasurement> afcMeasurements = client.getLatestObservationsBySensor(new Filter()
+                    .limit(1).entityNames(afcMultiSensor.getResourceId())
+            );
+            final Supplier<IllegalArgumentException> exception = () -> new IllegalArgumentException("Can not find Thing with @iot.id \"" + resourceUrn + "\".");
+
+            Optional<ResourceMeasurement> afcMeasurementOpt = ofNullable(afcMeasurements.size() == 1 ? afcMeasurements.get(0) : null);
+            ResourceMeasurement afcMeasurement = afcMeasurementOpt.orElseThrow(exception);
+
+            AFCLocationList afcLocations = AFCLocationUtils.sort(afcMeasurement);
+            Optional<AFCAggrLocation> afcLastLocationOpt = ofNullable(afcLocations.getFirst());
+            AFCAggrLocation afcLastLocation = afcLastLocationOpt.orElseThrow(exception);
+
+            Location ogcLocation = Converter.convertToLocation(afcMultiSensor, afcLastLocation, uriComponent);
+            List<Location> ogcLocations = Collections.singletonList(ogcLocation);
+            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(encode(ogcLocations));
         });
 
-        router().get(create("FeaturesOfInterest(:id)")).handler(ctx -> {
+        router().get(create("HistoricalLocations(:id)/Locations")).handler(ctx -> {
             RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
-            String id = ctx.pathParam("id");
+            String id = ctx.pathParam("id"); // resourceUrn + observedProperty + time
+            ctx.reroute(format("%s/Locations(%s)", uriComponent.getGatewayPath(), id));
+        });
 
-            FeatureOfInterest featureOfInterest = new FeatureOfInterest();
-            featureOfInterest.setId(id);
-            featureOfInterest.setSelfLink(format("%s/FeaturesOfInterest(%s)", uriComponent.getGatewayUri(), id));
-            featureOfInterest.setObservationsNavigationLink(format("FeaturesOfInterest(%s)/Observations", id));
-            featureOfInterest.setName("Weather Station YYC.");
-            featureOfInterest.setDescription("This is a weather station located at the Calgary Airport.");
-            featureOfInterest.setEncodingType("application/vnd.geo+json");
+        router().get(create("HistoricalLocations(:id)")).handler(ctx -> {
+            RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
+            String id = ctx.pathParam("id"); // resourceUrn + observedProperty + time
 
-            FeatureOfInterest.Feature feature = new FeatureOfInterest.Feature();
-            feature.setType("Feature");
-            featureOfInterest.setFeature(feature);
+            String [] idCmp = Converter.disassemblyId(id);
+            String resourceUrn = idCmp[0], observedProperty = idCmp[1], time = idCmp[2];
+            final DateTimeFormatter formatter = new DateTimeFormatterBuilder().append(ofPattern("yyyy-MM-dd HH:mm"))
+                    .toFormatter().withZone(ZoneId.of("UTC"));
 
-            Geometry geometry = new Geometry();
-            geometry.setType("Point");
-            geometry.setCoordinates(asList(-114.06, 51.05));
-            feature.setGeometry(geometry);
+            Instant startTime = Instant.parse(time);
+            Instant endTime = startTime.plusSeconds(60);
 
-            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(featureOfInterest.encode());
+            MultiSensor afcMultiSensor = client.getSensorByResourceUrn(resourceUrn);
+            List<ResourceMeasurement> afcMeasurements = client.getObservationsBySensor(new Filter()
+                    .startTime(formatter.format(startTime)).endTime(formatter.format(endTime))
+                    .entityNames(afcMultiSensor.getResourceId()).measurements(observedProperty)
+            );
+            final Supplier<IllegalArgumentException> exception = () -> new IllegalArgumentException("Can not find Location with @iot.id \"" + resourceUrn + "\".");
+
+            Optional<ResourceMeasurement> afcMeasurementOpt = ofNullable(afcMeasurements.size() == 1 ? afcMeasurements.get(0) : null);
+            ResourceMeasurement afcMeasurement = afcMeasurementOpt.orElseThrow(exception);
+
+            AFCLocationList afcLocations = AFCLocationUtils.sort(afcMeasurement);
+            Optional<AFCAggrLocation> afcLastLocationOpt = ofNullable(afcLocations.getLast());
+            AFCAggrLocation afcLocation = afcLastLocationOpt.orElseThrow(exception);
+
+            HistoricalLocation ogcLocation = Converter.convertToHistoricalLocation(afcMultiSensor, afcLocation, uriComponent);
+            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(ogcLocation.encode());
+        });
+
+        // without filter
+        router().get(create("Things(:id)/HistoricalLocations")).handler(ctx -> {
+            RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
+            String resourceUrn = ctx.pathParam("id"); // resourceUrn
+            String filter = ctx.request().getParam("filter"); // TODO filter
+
+            final DateTimeFormatter formatter = new DateTimeFormatterBuilder().append(ofPattern("yyyy-MM-dd HH:mm"))
+                    .toFormatter().withZone(ZoneId.of("UTC"));
+
+            MultiSensor afcMultiSensor = client.getSensorByResourceUrn(resourceUrn);
+            List<ResourceMeasurement> afcMeasurements;
+            if (filter != null && !filter.isEmpty()) {
+                String time = ""; // TODO parse filter
+
+                Instant startTime = Instant.parse(time);
+                Instant endTime = startTime.plusSeconds(60);
+
+                afcMeasurements = client.getObservationsBySensor(new Filter()
+                        .startTime(formatter.format(startTime)).endTime(formatter.format(endTime))
+                        .entityNames(afcMultiSensor.getResourceId())
+                );
+            } else {
+                afcMeasurements = client.getLatestObservationsBySensor(new Filter()
+                        .limit(1).entityNames(afcMultiSensor.getResourceId())
+                );
+            }
+
+            final Supplier<IllegalArgumentException> exception = () -> new IllegalArgumentException("Can not find HistoricalLocations of the Thing with @iot.id \"" + resourceUrn + "\".");
+
+            Optional<ResourceMeasurement> afcMeasurementOpt = ofNullable(afcMeasurements.size() == 1 ? afcMeasurements.get(0) : null);
+            ResourceMeasurement afcMeasurement = afcMeasurementOpt.orElseThrow(exception);
+
+            AFCLocationList afcLocations = AFCLocationUtils.sort(afcMeasurement);
+
+            List<HistoricalLocation> locations = Converter.convertToHistoricalLocation(afcMultiSensor, afcLocations.getList(), uriComponent);
+            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(encode(locations));
         });
+
+        // without filter
+        router().get(create("Locations(:id)/HistoricalLocations")).handler(ctx -> {
+            RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
+            String id = ctx.pathParam("id"); // resourceUrn + observedProperty + time
+            String filter = ctx.request().getParam("filter"); // TODO filter
+
+            String [] idCmp = Converter.disassemblyId(id);
+            String resourceUrn = idCmp[0], observedProperty = idCmp[1], time = idCmp[2];
+
+            final DateTimeFormatter formatter = new DateTimeFormatterBuilder().append(ofPattern("yyyy-MM-dd HH:mm"))
+                    .toFormatter().withZone(ZoneId.of("UTC"));
+
+            final Supplier<IllegalArgumentException> exception =
+                    () -> new IllegalArgumentException("Can not find HistoricalLocations of the Thing with @iot.id \"" + resourceUrn + "\".");
+
+            MultiSensor afcMultiSensor = client.getSensorByResourceUrn(resourceUrn);
+
+            Instant startTime = Instant.parse(time);
+            Instant endTime = startTime.plusSeconds(60*60); // 1h
+
+            List<ResourceMeasurement> afcMeasurements = client.getObservationsBySensor(new Filter()
+                    .startTime(formatter.format(startTime)).endTime(formatter.format(endTime))
+                    .entityNames(afcMultiSensor.getResourceId()).measurements(observedProperty)
+            );
+
+            Optional<ResourceMeasurement> afcMeasurementOpt = ofNullable(afcMeasurements.size() == 1 ? afcMeasurements.get(0) : null);
+            ResourceMeasurement afcMeasurement = afcMeasurementOpt.orElseThrow(exception);
+
+            AFCLocationList afcLocations = AFCLocationUtils.sort(afcMeasurement);
+
+            List<HistoricalLocation> locations = Converter.convertToHistoricalLocation(afcMultiSensor, afcLocations.getList(), uriComponent);
+            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(encode(locations));
+        });
+    }
+
+    private static class AFCAggrLocation implements Comparable<AFCAggrLocation> {
+        private final String measurement;
+        private final io.connector.model.afarcloud.Observation observation;
+
+        public AFCAggrLocation(String measurement, io.connector.model.afarcloud.Observation observation) {
+            this.measurement = measurement;
+            this.observation = observation;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            AFCAggrLocation that = (AFCAggrLocation) o;
+            return this.compareTo(that) == 0;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(measurement, observation);
+        }
+
+        @Override
+        public int compareTo(AFCAggrLocation o) {
+            io.connector.model.afarcloud.Observation o1 = this.observation;
+            io.connector.model.afarcloud.Observation o2 = o.observation;
+
+            Instant o1Time = Instant.parse(o1.getTime());
+            Instant o2Time = Instant.parse(o2.getTime());
+
+            if (o1Time.equals(o2Time)) {
+                Double o1Dist = sqrt(pow(o1.getLongitude(), 2) + pow(o1.getLatitude(), 2) + pow(o1.getAltitude(), 2));
+                Double o2Dist = sqrt(pow(o2.getLongitude(), 2) + pow(o2.getLatitude(), 2) + pow(o2.getAltitude(), 2));
+                return o1Dist.compareTo(o2Dist);
+            } else {
+                return o1Time.compareTo(o2Time);
+            }
+        }
+    }
+
+    private static class AFCLocationList {
+        private final List<AFCAggrLocation> list;
+
+        AFCLocationList() {
+            this.list = new ArrayList<>();
+        }
+
+        public void sort() {
+            Collections.sort(list);
+        }
+
+        public void add(AFCAggrLocation location) {
+            list.add(location);
+        }
+
+        public AFCAggrLocation getFirst() {
+            return list.isEmpty() ? null : list.get(0);
+        }
+
+        public AFCAggrLocation getLast() {
+            return list.isEmpty() ? null : list.get(list.size()-1);
+        }
+
+        public AFCAggrLocation get(int index) {
+            return list.get(index);
+        }
+
+        public List<AFCAggrLocation> getList() {
+            return list;
+        }
+    }
+
+    private static class AFCLocationUtils {
+
+        static AFCLocationList sort(ResourceMeasurement afcMeasurement) {
+            Objects.requireNonNull(afcMeasurement);
+
+            AFCLocationList aggrLocations = new AFCLocationList();
+            for (SensorTelemetry measurement : afcMeasurement.getMeasurements()) {
+                for (io.connector.model.afarcloud.Observation observation : measurement.getObservations()) {
+                    aggrLocations.add(new AFCAggrLocation(measurement.getMeasurement(), observation));
+                }
+            }
+            aggrLocations.sort();
+
+            List<AFCAggrLocation> list = aggrLocations.list;
+            if (list.size() > 1) {
+                Iterator<AFCAggrLocation> iterator = list.iterator();
+                AFCAggrLocation previous = iterator.next();
+                while (iterator.hasNext()) {
+                    AFCAggrLocation current = iterator.next();
+                    if (previous.equals(current)) {
+                        iterator.remove(); continue;
+                    }
+                    previous = current;
+                }
+            }
+
+            return aggrLocations;
+        }
     }
 
     private static class Converter {
 
-        static Sensor convertSensor(Unit afcUnit, RequestUriComponent uriComponent) {
-            Sensor ogcSensor = new Sensor();
-            ogcSensor.setId(afcUnit.getResourceId());
-            ogcSensor.setSelfLink(format("%s/Sensors(%s)", uriComponent.getGatewayUri(), afcUnit.getResourceId()));
-            ogcSensor.setDataStreamNavigationLink("unknown"); // TODO add Datastream navigation link
-            ogcSensor.setName(afcUnit.getResourceType());
-            ogcSensor.setDescription(afcUnit.getResourceUrn());
-            ogcSensor.setEncodingType("unknown");
-            ogcSensor.setMetadata("none");
-            return ogcSensor;
+        private static final String DELIMITER = String.valueOf(0x2b); //
+
+        static String assemblyId(String... parts) {
+            return String.join(DELIMITER, parts);
+        }
+        
+        static String[] disassemblyId(String id) {
+            return id.split(DELIMITER);
+        }
+
+        static HistoricalLocation convertToHistoricalLocation(MultiSensor afcMultiSensor, AFCAggrLocation afcLocation, RequestUriComponent uriComponent) {
+            HistoricalLocation historicalLocation = new HistoricalLocation();
+            String locationId = assemblyId(afcMultiSensor.getResourceUrn(), afcLocation.measurement, afcLocation.observation.getTime());
+            historicalLocation.setId(locationId);
+            historicalLocation.setSelfLink(format("%s/HistoricalLocations(%s)", uriComponent.getGatewayUri(), locationId));
+            historicalLocation.setLocationsNavigationLink(format("HistoricalLocations(%s)/Locations", locationId));
+            historicalLocation.setThingNavigationLink(format("HistoricalLocations(%s)/Thing", locationId));
+            historicalLocation.setTime(afcLocation.observation.getTime());
+            return historicalLocation;
+        }
+
+        static List<HistoricalLocation> convertToHistoricalLocation(MultiSensor afcMultiSensor, List<AFCAggrLocation> afcLocations, RequestUriComponent uriComponent) {
+            List<HistoricalLocation> historicalLocations = new ArrayList<>(afcLocations.size());
+            for (AFCAggrLocation afcLocation : afcLocations) {
+                historicalLocations.add(convertToHistoricalLocation(afcMultiSensor, afcLocation, uriComponent));
+            }
+            return historicalLocations;
+        }
+
+        static Location convertToLocation(MultiSensor afcMultiSensor, AFCAggrLocation afcLocation, RequestUriComponent uriComponent) {
+            Location location = new Location();
+            String locationId = assemblyId(afcMultiSensor.getResourceUrn(), afcLocation.measurement, afcLocation.observation.getTime());
+            location.setId(locationId);
+            location.setSelfLink(format("%s/Locations(%s)", uriComponent.getGatewayUri(), locationId));
+            location.setHistoricalLocationsNavigationLink(format("Locations(%s)/HistoricalLocations", locationId));
+            location.setName(afcMultiSensor.getResourceType());
+            location.setDescription(afcLocation.observation.getProvider());
+            location.setEncodingType("application/vnd.geo+json");
+
+            Location.Info info = new Location.Info();
+            info.setType("Feature");
+            location.setLocation(info);
+
+            Geometry geometry = new Geometry();
+            geometry.setType("Point");
+            io.connector.model.afarcloud.Observation afcObservation = afcLocation.observation;
+            geometry.setCoordinates(asList(afcObservation.getLongitude(), afcObservation.getLatitude(), afcObservation.getAltitude()));
+            info.setGeometry(geometry);
+
+            return location;
+        }
+
+        static Observation convertToObservation(ResourceMeasurement afcMeasurement, SensorTelemetry afcTelemetry, io.connector.model.afarcloud.Observation afcObservation, RequestUriComponent uriComponent) {
+            Observation observation = new Observation();
+            String observationId = assemblyId(afcMeasurement.getResource(), afcTelemetry.getMeasurement(), afcObservation.getTime());
+            observation.setId(observationId);
+            observation.setSelfLink(format("%s/Observations(%s)", uriComponent.getGatewayUri(), observationId));
+            // observation.setFeatureOfInterestNavigationLink(format("Observations(%s)/FeatureOfInterest", observationId));
+            observation.setFeatureOfInterestNavigationLink("https://storage07-afarcloud.qa.pdmfc.com/storage/rest/registry/getAllObservationTypes");
+            observation.setDataStreamNavigationLink(format("Observations(%s)/Datastream", observationId));
+            observation.setPhenomenonTime(afcObservation.getTime());
+            observation.setResultTime(afcObservation.getTime());
+            observation.setResult(afcObservation.getValue());
+            return observation;
+        }
+
+        static ObservedProperty convertToObservedProperty(MultiSensor afcMultiSensor, MultiSensor.SensorSchema afcSensor, RequestUriComponent uriComponent) {
+            Objects.requireNonNull(afcMultiSensor);
+            Objects.requireNonNull(afcSensor);
+            Objects.requireNonNull(uriComponent);
+
+            ObservedProperty observedProperty = new ObservedProperty();
+            String observedPropertyId = assemblyId(afcMultiSensor.getResourceUrn(), afcSensor.getObservedProperty());
+            observedProperty.setId(observedPropertyId);
+            observedProperty.setSelfLink(format("%s/ObservedProperties(%s)", uriComponent.getGatewayUri(), observedPropertyId));
+            observedProperty.setDataStreamNavigationLink(format("ObservedProperties(%s)/Datastream", observedPropertyId));
+            observedProperty.setName(afcSensor.getObservedProperty());
+            observedProperty.setDescription(afcSensor.getObservedProperty());
+            observedProperty.setDefinition(afcSensor.getUom());
+            return observedProperty;
+        }
+
+        static Thing convertToThing(MultiSensor afcMultiSensor, RequestUriComponent uriComponent) {
+            Objects.requireNonNull(afcMultiSensor);
+            Objects.requireNonNull(uriComponent);
+
+            Thing thing = new Thing();
+            String thingId = afcMultiSensor.getResourceUrn();
+            thing.setId(thingId);
+            thing.setSelfLink(format("%s/Things(%s)", uriComponent.getGatewayUri(), thingId));
+            thing.setLocationsNavigationLink(format("Things(%s)/Locations", thingId));
+            thing.setDataStreamNavigationLink(format("Things(%s)/Datastreams", thingId));
+            thing.setHistoricalLocationsNavigationLink(format("Things(%s)/HistoricalLocations", thingId));
+            thing.setName(afcMultiSensor.getResourceType());
+            thing.setDescription(afcMultiSensor.getResourceType());
+            thing.setProperties(null);
+            return thing;
         }
 
-        static List<Sensor> convertSensors(List<Unit> afcUnits, RequestUriComponent uriComponent) {
-            List<Sensor> ogcSensors = new ArrayList<>(afcUnits.size());
-            for (Unit afcUnit : afcUnits) {
-                ogcSensors.add(convertSensor(afcUnit, uriComponent));
+        static List<Thing> convertToThing(List<MultiSensor> afcMultiSensors, RequestUriComponent uriComponent) {
+            List<Thing> ogcThings = new ArrayList<>(afcMultiSensors.size());
+            for (MultiSensor afcMultiSensor : afcMultiSensors) {
+                ogcThings.add(convertToThing(afcMultiSensor, uriComponent));
             }
-            return ogcSensors;
+            return ogcThings;
         }
+
+        static Datastream convertToDataStream(MultiSensor afcMultiSensor, MultiSensor.SensorSchema afcSensor, SensorTelemetry telemetry, RequestUriComponent uriComponent) {
+            Objects.requireNonNull(afcMultiSensor);
+            Objects.requireNonNull(afcSensor);
+            Objects.requireNonNull(uriComponent);
+
+            Datastream datastream = new Datastream();
+            String dataStreamId = assemblyId(afcMultiSensor.getResourceUrn(), afcSensor.getObservedProperty());
+            datastream.setId(dataStreamId);
+            datastream.setSelfLink(format("%s/Datastreams(%s)", uriComponent.getGatewayUri(), dataStreamId));
+            datastream.setThingNavigationLink(format("Datastreams(%s)/Thing", dataStreamId));
+            datastream.setSensorNavigationLink(format("Datastreams(%s)/Sensor", dataStreamId));
+            datastream.setObservedPropertyNavigationLink(format("Datastreams(%s)/ObservedProperty", dataStreamId));
+            datastream.setObservationsNavigationLink(format("Datastreams(%s)/Observations", dataStreamId));
+            datastream.setName(afcSensor.getObservedProperty());
+            datastream.setDescription(afcSensor.getObservedProperty());
+
+            Datastream.UnitOfMeasurement uom = new Datastream.UnitOfMeasurement();
+            uom.setName(afcSensor.getObservedProperty());
+            uom.setSymbol("");
+            uom.setDefinition(afcSensor.getUom());
+            datastream.setUnitOfMeasurement(uom);
+
+            datastream.setObservationType("http://www.opengis.net/def/observationType/OGC-OM/2.0/OM_Measurement");
+
+            Geometry geometry = new Geometry();
+            geometry.setType("Point");
+            geometry.setCoordinates(asList(afcMultiSensor.getLongitude(), afcMultiSensor.getLatitude()));
+            datastream.setObservedArea(geometry);
+
+            String startDate = "<none>";
+            String endDate = telemetry == null || telemetry.getObservations().isEmpty() ? "<none>" : telemetry.getObservations().get(0).getTime();
+            String time = startDate + "/" + endDate;
+            datastream.setPhenomenonTime(time);
+            datastream.setResultTime(time);
+
+            return datastream;
+        }
+
+        static List<Datastream> convertToDataStream(MultiSensor afcMultiSensor, ResourceMeasurement measurement, RequestUriComponent uriComponent) {
+            Objects.requireNonNull(afcMultiSensor);
+            Objects.requireNonNull(uriComponent);
+
+            List<Datastream> datastreams = new ArrayList<>(afcMultiSensor.getObservations().size());
+
+            Map<String, SensorTelemetry> sensors = new HashMap<>();
+            if (measurement != null) {
+                for (SensorTelemetry telemetry : measurement.getMeasurements()) {
+                    sensors.put(telemetry.getMeasurement(), telemetry);
+                }
+            }
+
+            for (MultiSensor.SensorSchema sensor : afcMultiSensor.getObservations()) {
+                datastreams.add(convertToDataStream(afcMultiSensor, sensor, sensors.get(sensor.getObservedProperty()), uriComponent));
+            }
+
+            return datastreams;
+        }
+
+        static Sensor convertToSensor(MultiSensor afcMultiSensor, RequestUriComponent uriComponent) {
+            Objects.requireNonNull(afcMultiSensor);
+            Objects.requireNonNull(uriComponent);
+
+            String afcDomain = "https://storage07-afarcloud.qa.pdmfc.com/storage/rest";
+
+            Sensor sensor = new Sensor();
+            String thingId = afcMultiSensor.getResourceUrn();
+            sensor.setId(thingId);
+            sensor.setSelfLink(format("%s/Sensors(%s)", uriComponent.getGatewayUri(), thingId));
+            sensor.setDataStreamNavigationLink(format("Sensors(%s)/Datastreams", thingId));
+            sensor.setName(afcMultiSensor.getResourceType());
+            sensor.setDescription(afcMultiSensor.getResourceType());
+            sensor.setEncodingType("application/json");
+            sensor.setMetadata(format("%s/registry/getSensor/%s", afcDomain, afcMultiSensor.getResourceId()));
+
+            return sensor;
+        }
+
     }
 }

+ 18 - 18
connector-module-ogc-sensorthings/src/main/java/io/connector/module/ogc/sensorthings/gateway/AFarCloudGateway.java

@@ -88,7 +88,7 @@ public class AFarCloudGateway extends AbstractGateway {
 
                 Observation observation = new Observation();
                 observation.setTime("2020-04-19T14:17:00Z");
-                observation.setAltitude(282);
+                observation.setAltitude(282.);
                 observation.setLatitude(50.0393219);
                 observation.setLongitude(14.6106731);
                 observation.setGeohash("u2fksmrhjmeq");
@@ -98,7 +98,7 @@ public class AFarCloudGateway extends AbstractGateway {
                 observation.setService("environmentalObservations");
                 observation.setProvider("LESP");
                 observation.setType("air_sensor");
-                observation.setUom("http://qudt.org/vocab/unit/M-PER-SEC");
+                observation.setUom("http://qudt.org/vocab/multiSensor/M-PER-SEC");
                 telemetry.addObservation(observation);
 
                 telemetries.add(telemetry);
@@ -151,7 +151,7 @@ public class AFarCloudGateway extends AbstractGateway {
             for (int i = 0; i < limit; i++) {
                 Observation observation = new Observation();
                 observation.setTime("2020-04-19T14:17:00Z");
-                observation.setAltitude(282);
+                observation.setAltitude(282.);
                 observation.setLatitude(50.0393219);
                 observation.setLongitude(14.6106731);
                 observation.setGeohash("u2fksmrhjmeq");
@@ -161,7 +161,7 @@ public class AFarCloudGateway extends AbstractGateway {
                 observation.setService("environmentalObservations");
                 observation.setProvider("LESP");
                 observation.setType("air_sensor");
-                observation.setUom("http://qudt.org/vocab/unit/M-PER-SEC");
+                observation.setUom("http://qudt.org/vocab/multiSensor/M-PER-SEC");
                 telemetry.addObservation(observation);
             }
 
@@ -256,26 +256,26 @@ public class AFarCloudGateway extends AbstractGateway {
 
             String resourceId = ctx.pathParam("id");
 
-            Unit unit = new Unit();
-            unit.setResourceId(resourceId);
-            unit.setResourceType("AFC 8CF95740000008AE");
-            unit.setResourceUrn("urn:afc:AS07:enviro:IMA:air_quality:10002222");
-            unit.setLatitude(50.0382589);
-            unit.setLongitude(14.6112164);
-            unit.setAltitude(280.0);
-            unit.setPreprocessing(false);
-            unit.setPythonScript("");
-
-            Unit.SensorSchema sensorSchema = new Unit.SensorSchema();
+            MultiSensor multiSensor = new MultiSensor();
+            multiSensor.setResourceId(resourceId);
+            multiSensor.setResourceType("AFC 8CF95740000008AE");
+            multiSensor.setResourceUrn("urn:afc:AS07:enviro:IMA:air_quality:10002222");
+            multiSensor.setLatitude(50.0382589);
+            multiSensor.setLongitude(14.6112164);
+            multiSensor.setAltitude(280.0);
+            multiSensor.setPreprocessing(false);
+            multiSensor.setPythonScript("");
+
+            MultiSensor.SensorSchema sensorSchema = new MultiSensor.SensorSchema();
             sensorSchema.setObservedProperty("air_temperature");
-            sensorSchema.setUom("http://qudt.org/vocab/unit/DEG_C");
+            sensorSchema.setUom("http://qudt.org/vocab/multiSensor/DEG_C");
             sensorSchema.setAccuracy(0.1);
             sensorSchema.setPropertyId(456);
             sensorSchema.setMin_value(-40.0);
             sensorSchema.setMax_value(60.0);
-            unit.setObservations(singletonList(sensorSchema));
+            multiSensor.setObservations(singletonList(sensorSchema));
 
-            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(encode(unit));
+            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(encode(multiSensor));
         });
 
         router().get(create("registry/getCollar/:id")).handler(ctx -> {

+ 36 - 36
connector-module-senslog1/src/main/java/io/connector/module/senslog1/SensLog1HttpClient.java

@@ -106,15 +106,15 @@ public class SensLog1HttpClient {
     public void uploadPositions(List<UnitData> unitData) {
         HostConfig host = config.getFeederServiceHost();
 
-        for (UnitData unit : unitData) {
-            for (Position position : unit.getPositions()) {
+        for (UnitData multiSensor : unitData) {
+            for (Position position : multiSensor.getPositions()) {
 
                 HttpRequest request = HttpRequest.newBuilder()
                         .contentType(TEXT_PLAIN)
                         .url(URLBuilder.newBuilder(host.getDomain(), host.getPath())
                                 .addParam("Operation", "InsertPosition")
                                 .addParam("date", position.getTimestamp().format(FORMATTER))
-                                .addParam("unit_id", unit.getId())
+                                .addParam("unit_id", multiSensor.getId())
                                 .addParam("lat", position.getLatitude())
                                 .addParam("lon", position.getLongitude())
                                 .addParam("alt", position.getAltitude())
@@ -144,8 +144,8 @@ public class SensLog1HttpClient {
         HostConfig host = config.getFeederServiceHost();
 
         Map<Long, UnitData> badObservations = new HashMap<>();
-        for (UnitData unit : unitData) {
-            for (SensorData sensor : unit.getSensors()) {
+        for (UnitData multiSensor : unitData) {
+            for (SensorData sensor : multiSensor.getSensors()) {
                 for (Observation observation : sensor.getObservations()) {
 
 
@@ -155,7 +155,7 @@ public class SensLog1HttpClient {
                                     .addParam("Operation", "InsertObservation")
                                     .addParam("value", observation.getValue())
                                     .addParam("date", observation.getTimestamp().format(FORMATTER))
-                                    .addParam("unit_id", unit.getId())
+                                    .addParam("unit_id", multiSensor.getId())
                                     .addParam("sensor_id", sensor.getId())
                                     .build())
                             .GET().build();
@@ -173,12 +173,12 @@ public class SensLog1HttpClient {
                     } else {
 
                         SensorObservation sensorObservation = badObservations
-                                .computeIfAbsent(unit.getId(), UnitData::new)
+                                .computeIfAbsent(multiSensor.getId(), UnitData::new)
                                 .getSensor(sensor.getId());
 
                         if (sensorObservation == null) {
                             sensorObservation = new SensorObservation(sensor.getId());
-                            badObservations.get(unit.getId()).addSensor(sensorObservation);
+                            badObservations.get(multiSensor.getId()).addSensor(sensorObservation);
                         }
 
                         sensorObservation.addObservation(observation);
@@ -196,8 +196,8 @@ public class SensLog1HttpClient {
 
     public List<UnitData> positions(OffsetDateTime fromDate, OffsetDateTime toDate) {
         List<UnitData> units = new ArrayList<>();
-        for (UnitInfo unit : getUnitInfos().values()) {
-            units.addAll(positions(unit.getId(), fromDate, toDate));
+        for (UnitInfo multiSensor : getUnitInfos().values()) {
+            units.addAll(positions(multiSensor.getId(), fromDate, toDate));
         }
         return units;
     }
@@ -208,7 +208,7 @@ public class SensLog1HttpClient {
         return Collections.singletonList(unitData);
     }
 
-    private void positions(UnitData unit, OffsetDateTime fromDate, OffsetDateTime toDate) {
+    private void positions(UnitData multiSensor, OffsetDateTime fromDate, OffsetDateTime toDate) {
         HostConfig host = config.getDataServiceHost();
         logger.info("Getting observations from {}.", host.getDomain());
 
@@ -216,7 +216,7 @@ public class SensLog1HttpClient {
                 .url(URLBuilder.newBuilder(host.getDomain(), host.getPath())
                         .addParam("Operation", "GetPositionsDay")
                         .addParam("user", config.getUser())
-                        .addParam("unit_id", unit.getId())
+                        .addParam("unit_id", multiSensor.getId())
                         .addParam("fromTime", fromDate.format(FORMATTER))
                         .addParam("toTime", toDate.format(FORMATTER))
                         .build())
@@ -233,7 +233,7 @@ public class SensLog1HttpClient {
                     Position position = Position.parse((JsonObject)objJson);
 
                     if (position != null) {
-                        unit.addPosition(position);
+                        multiSensor.addPosition(position);
                     }
                 }
             }
@@ -260,7 +260,7 @@ public class SensLog1HttpClient {
     public List<Unit> observationsWithInfo(long unitId, OffsetDateTime fromDate, OffsetDateTime toDate) {
         UnitInfo unitInfo = getUnitInfos().get(unitId);
         if (unitInfo == null) {
-            throw new RuntimeException(format("Unknown unit with id %s.", unitId));
+            throw new RuntimeException(format("Unknown multiSensor with id %s.", unitId));
         }
 
         UnitData unitData = new UnitData(unitInfo.getId());
@@ -279,11 +279,11 @@ public class SensLog1HttpClient {
     public List<Unit> observationsWithInfo(long unitId, long sensorId, OffsetDateTime fromDate, OffsetDateTime toDate) {
         UnitInfo unitInfo = getUnitInfos().get(unitId);
         if (unitInfo == null) {
-            throw new RuntimeException(format("Unknown unit with id %s.", unitId));
+            throw new RuntimeException(format("Unknown multiSensor with id %s.", unitId));
         }
         SensorInfo sensorInfo = unitInfo.getSensor(sensorId);
         if (sensorInfo == null) {
-            throw new RuntimeException(format("Unknown sensor with id %s for the unit %s.", sensorId, unitId));
+            throw new RuntimeException(format("Unknown sensor with id %s for the multiSensor %s.", sensorId, unitId));
         }
         UnitData unitData = new UnitData(unitInfo.getId());
         SensorData sensorObs = new SensorData(sensorInfo.getId());
@@ -298,8 +298,8 @@ public class SensLog1HttpClient {
 
     public List<UnitData> observations(OffsetDateTime fromDate, OffsetDateTime toDate) {
         List<UnitData> units = new ArrayList<>();
-        for (UnitInfo unit : getUnitInfos().values()) {
-            units.addAll(observations(unit.getId(), fromDate, toDate));
+        for (UnitInfo multiSensor : getUnitInfos().values()) {
+            units.addAll(observations(multiSensor.getId(), fromDate, toDate));
         }
         return units;
     }
@@ -307,26 +307,26 @@ public class SensLog1HttpClient {
     public List<UnitData> observations(long unitId, OffsetDateTime fromDate, OffsetDateTime toDate) {
         UnitInfo unitInfo = getUnitInfos().get(unitId);
         if (unitInfo != null) {
-            UnitData unit = new UnitData(unitInfo.getId());
+            UnitData multiSensor = new UnitData(unitInfo.getId());
             for (SensorInfo sensorInfo : unitInfo.getSensors()) {
                 SensorData sensor = new SensorData(sensorInfo.getId());
-                unit.addSensor(sensor);
-                observations(unit, sensor, fromDate, toDate);
+                multiSensor.addSensor(sensor);
+                observations(multiSensor, sensor, fromDate, toDate);
             }
-            return Arrays.asList(unit);
+            return Arrays.asList(multiSensor);
         }
         return Collections.emptyList();
     }
 
     public List<UnitData> observations(long unitId, long sensorId, OffsetDateTime fromDate, OffsetDateTime toDate) {
-        UnitData unit = new UnitData(unitId);
+        UnitData multiSensor = new UnitData(unitId);
         SensorData sensor = new SensorData(sensorId);
-        unit.addSensor(sensor);
-        observations(unit, sensor, fromDate, toDate);
-        return Arrays.asList(unit);
+        multiSensor.addSensor(sensor);
+        observations(multiSensor, sensor, fromDate, toDate);
+        return Arrays.asList(multiSensor);
     }
 
-    private void observations(UnitData unit, SensorData sensor, OffsetDateTime fromDate, OffsetDateTime toDate) {
+    private void observations(UnitData multiSensor, SensorData sensor, OffsetDateTime fromDate, OffsetDateTime toDate) {
 
         HostConfig host = config.getSensorServiceHost();
         logger.info("Getting observations from {}.", host.getDomain());
@@ -335,7 +335,7 @@ public class SensLog1HttpClient {
                 .url(URLBuilder.newBuilder(host.getDomain(), host.getPath())
                         .addParam("Operation", "GetObservations")
                         .addParam("user", config.getUser())
-                        .addParam("unit_id", unit.getId())
+                        .addParam("unit_id", multiSensor.getId())
                         .addParam("sensor_id", sensor.getId())
                         .addParam("from", fromDate.format(FORMATTER))
                         .addParam("to", toDate.format(FORMATTER))
@@ -383,17 +383,17 @@ public class SensLog1HttpClient {
             unitInfoList = new HashMap<>(json.size());
             for (Object unitJson : json) {
                 if (unitJson instanceof JsonObject) {
-                    UnitInfo unit = UnitInfo.parse((JsonObject)unitJson);
-                    if (unit != null) {
-                        units.add(unit);
-                        unitInfoList.put(unit.getId(), unit);
+                    UnitInfo multiSensor = UnitInfo.parse((JsonObject)unitJson);
+                    if (multiSensor != null) {
+                        units.add(multiSensor);
+                        unitInfoList.put(multiSensor.getId(), multiSensor);
                     }
                 }
             }
 
-            for (UnitInfo unit : units) {
-                List<SensorInfo> sensors = sensors(unit.getId());
-                unit.setSensors(sensors);
+            for (UnitInfo multiSensor : units) {
+                List<SensorInfo> sensors = sensors(multiSensor.getId());
+                multiSensor.setSensors(sensors);
             }
 
             return units;
@@ -435,7 +435,7 @@ public class SensLog1HttpClient {
                 }
             }
 
-            logger.info("For the unit {} was added {} sensors.", unitId, sensors.size());
+            logger.info("For the multiSensor {} was added {} sensors.", unitId, sensors.size());
 
             return sensors;
         } else {

+ 7 - 7
connector-module-senslog1/src/main/java/io/connector/module/senslog1/gateway/SensLog1Gateway.java

@@ -42,10 +42,10 @@ public class SensLog1Gateway extends AbstractGateway {
 
             AllowedStation allowedStation = schedulerConfig.getAllowedStations();
             JsonArray stations = new JsonArray();
-            for (UnitData unit : unitData) {
-                if (allowedStation.isAllowed(Long.toString(unit.getId()))) {
+            for (UnitData multiSensor : unitData) {
+                if (allowedStation.isAllowed(Long.toString(multiSensor.getId()))) {
                     OffsetDateTime from = MAX, to = MIN;
-                    for (SensorData sensor : unit.getSensors()) {
+                    for (SensorData sensor : multiSensor.getSensors()) {
                         for (Observation observation : sensor.getObservations()) {
                             OffsetDateTime time = observation.getTimestamp();
                             if (time.isBefore(from)) {
@@ -57,7 +57,7 @@ public class SensLog1Gateway extends AbstractGateway {
                         }
                     }
                     stations.add(new JsonObject()
-                            .put("id", unit.getId())
+                            .put("id", multiSensor.getId())
                             .put("fromDate", from.format(ISO_OFFSET_DATE_TIME))
                     );
                 }
@@ -209,11 +209,11 @@ public class SensLog1Gateway extends AbstractGateway {
         List<UnitData> result = new ArrayList<>();
 
         for (Map.Entry<Long, List<UnitData>> unitDataEntry : units.entrySet()) {
-            UnitData unit = new UnitData(unitDataEntry.getKey());
+            UnitData multiSensor = new UnitData(unitDataEntry.getKey());
             for (UnitData unitData : unitDataEntry.getValue()) {
-                unit.mergeIn(unitData);
+                multiSensor.mergeIn(unitData);
             }
-            result.add(unit);
+            result.add(multiSensor);
         }
 
         return result;