diff --git a/UI/src/assetTypeConfiguration.js b/UI/src/assetTypeConfiguration.js index a4aa626e6b..35b47b5e83 100644 --- a/UI/src/assetTypeConfiguration.js +++ b/UI/src/assetTypeConfiguration.js @@ -1022,14 +1022,15 @@ {label: 'Tien numero', type: 'read_only_number', publicId: "roadNumber", weight: 1, cssClass: 'road-number'}, {label: 'Tieosanumero', type: 'read_only_number', publicId: "roadPartNumber", weight: 2, cssClass: 'road-part-number'}, {label: 'Ajorata', type: 'read_only_number', publicId: "track", weight: 3, cssClass: 'track'}, - {label: 'Etäisyys tieosan alusta', type: 'read_only_number', publicId: "startAddrMValue", weight: 4, cssClass: 'start-addr-m'}, - {label: 'Etäisyys tieosan lopusta', type: 'read_only_number', publicId: "endAddrMValue", weight: 5, cssClass: 'end-addr-m'}, - {label: 'Hallinnollinen Luokka', type: 'read_only_text', publicId: "administrativeClass", weight: 6, cssClass: 'admin-class'}, + {label: 'Alkuetäisyys', type: 'read_only_number', publicId: "startAddrMValue", weight: 4, cssClass: 'start-addr-m'}, + {label: 'Loppuetäisyys', type: 'read_only_number', publicId: "endAddrMValue", weight: 5, cssClass: 'end-addr-m'}, + {label: 'Pituus', type: 'read_only_number', publicId: "addrLenght", weight: 6, cssClass: 'addr-lenght'}, + {label: 'Hallinnollinen Luokka', type: 'read_only_text', publicId: "administrativeClass", weight: 7, cssClass: 'admin-class'}, { label: 'Kaista', type: 'read_only_number', publicId: "lane_code", weight: 11, cssClass: 'lane-code' }, { - label: 'Kaistan tyypi', required: 'required', type: 'single_choice', publicId: "lane_type", + label: 'Kaistan tyyppi', required: 'required', type: 'single_choice', publicId: "lane_type", values: [ {id: 2, label: 'Ohituskaista'}, {id: 3, label: 'Kääntymiskaista oikealle'}, diff --git a/UI/src/controller/laneModellingCollection.js b/UI/src/controller/laneModellingCollection.js index 0a0fc03cf8..a1256d7cad 100644 --- a/UI/src/controller/laneModellingCollection.js +++ b/UI/src/controller/laneModellingCollection.js @@ -27,6 +27,13 @@ }); }; + self.getMainLaneByLinkIdAndSideCode = function(linkId, sideCode) { + return _.find(_.flatten(self.linearAssets), function(lane) { + var laneCode = _.head(Property.getPropertyByPublicId(lane.properties, 'lane_code').values).value; + return lane.linkId === linkId && laneCode === 1 && lane.sideCode === sideCode; + }); + }; + self.fetchViewOnlyLanes = function(boundingBox, zoom) { return backend.getViewOnlyLanesByBoundingBox(boundingBox, zoom, isWalkingCyclingActive).then(function(lanes) { eventbus.trigger('fetchedViewOnly', lanes); diff --git a/UI/src/model/selectedLaneModelling.js b/UI/src/model/selectedLaneModelling.js index c85df64747..ecc37bef94 100644 --- a/UI/src/model/selectedLaneModelling.js +++ b/UI/src/model/selectedLaneModelling.js @@ -185,6 +185,11 @@ return !_.isUndefined(lane); }; + this.isLaneFullLinkLength = function(lane) { + var selectedMainLane = self.getLane(1); + return lane.startMeasure !== selectedMainLane.startMeasure || lane.endMeasure !== selectedMainLane.endMeasure; + }; + this.haveNewLane = function () { return _.some(self.selection, function(lane){ return lane.id === 0; @@ -359,6 +364,32 @@ return getProperty(self.getLane(laneNumber, marker), 'properties'); }; + // Calculate accurate road address start and end m-values for additional lane. Get road link measures from main lane. + this.getAddressValuesForCutLane = function(lane) { + var mainLane = collection.getMainLaneByLinkIdAndSideCode(lane.linkId, lane.sideCode); + var roadLinkLength = mainLane.endMeasure; + var roadAddressSideCode = mainLane.roadAddressSideCode; + + // A coefficient is needed because road address and geometry lengths don't match exactly. Coefficient tells + // how long is '1 road address meter' on current link's geometry + var coefficient = (mainLane.endAddrMValue - mainLane.startAddrMValue) / roadLinkLength; + + // If road address side code is towards digitizing(2) startAddrMValue points to lane's start measure (southern end-point) + // and endAddrMValue points to lane's end measure (northern end-point) + if (roadAddressSideCode === 2) { + lane.startAddrMValue = mainLane.startAddrMValue + Math.round(lane.startMeasure * coefficient); + lane.endAddrMValue = mainLane.startAddrMValue + Math.round(lane.endMeasure * coefficient); + } + // If road address side code is against digitizing(3) startAddrMValue points to lane's end measure (northern end-point) + // and endAddrMValue points to lane's start measure (southern point) + else if (roadAddressSideCode === 3) { + lane.endAddrMValue = mainLane.endAddrMValue - Math.round(lane.startMeasure * coefficient); + lane.startAddrMValue = mainLane.endAddrMValue - Math.round(lane.endMeasure * coefficient); + } + + return lane; + }; + this.setNewLane = function(laneNumber) { var laneToClone; if(laneNumber == 2){ diff --git a/UI/src/view/linear_asset/laneModellingForm.js b/UI/src/view/linear_asset/laneModellingForm.js index 49326ff7a9..61b4c3b8f9 100644 --- a/UI/src/view/linear_asset/laneModellingForm.js +++ b/UI/src/view/linear_asset/laneModellingForm.js @@ -83,28 +83,29 @@ {label: 'Tien numero', type: 'read_only_number', publicId: "roadNumber", weight: 1, cssClass: 'road-number'}, {label: 'Tieosanumero', type: 'read_only_number', publicId: "roadPartNumber", weight: 2, cssClass: 'road-part-number'}, {label: 'Ajorata', type: 'read_only_number', publicId: "track", weight: 3, cssClass: 'track'}, - {label: 'Etäisyys tieosan alusta', type: 'read_only_number', publicId: "startAddrMValue", weight: 4, cssClass: 'start-addr-m'}, - {label: 'Etäisyys tieosan lopusta', type: 'read_only_number', publicId: "endAddrMValue", weight: 5, cssClass: 'end-addr-m'}, - {label: 'Hallinnollinen Luokka', type: 'read_only_text', publicId: "administrativeClass", weight: 6, cssClass: 'admin-class'}, - {label: 'Kaista', type: 'read_only_number', publicId: "lane_code", weight: 11, cssClass: 'lane-code'}, + {label: 'Alkuetäisyys', type: 'read_only_number', publicId: "startAddrMValue", weight: 4, cssClass: 'start-addr-m'}, + {label: 'Loppuetäisyys', type: 'read_only_number', publicId: "endAddrMValue", weight: 5, cssClass: 'end-addr-m'}, + {label: 'Pituus', type: 'read_only_number', publicId: "addrLenght", weight: 6, cssClass: 'addr-lenght'}, + {label: 'Hallinnollinen Luokka', type: 'read_only_text', publicId: "administrativeClass", weight: 7, cssClass: 'admin-class'}, + {label: 'Kaista', type: 'read_only_number', publicId: "lane_code", weight: 12, cssClass: 'lane-code'}, { - label: 'Kaistan tyypi', required: 'required', type: 'single_choice', publicId: "lane_type", defaultValue: "1", weight: 12, + label: 'Kaistan tyyppi', required: 'required', type: 'single_choice', publicId: "lane_type", defaultValue: "1", weight: 13, values: [ {id: 1, label: 'Pääkaista'} ] }, { - label: 'Alkupvm', type: 'date', publicId: "start_date", weight: 13, required: true + label: 'Alkupvm', type: 'date', publicId: "start_date", weight: 14, required: true } ] }; var roadAddressFormStructure = { fields : [ - {label: 'Osa', required: 'required', type: 'number', publicId: "startRoadPartNumber", weight: 7}, - {label: 'Etäisyys', required: 'required', type: 'number', publicId: "startDistance", weight: 8}, - {label: 'Osa', required: 'required', type: 'number', publicId: "endRoadPartNumber", weight: 9}, - {label: 'Etäisyys', required: 'required', type: 'number', publicId: "endDistance", weight: 10} + {label: 'Osa', required: 'required', type: 'number', publicId: "startRoadPartNumber", weight: 8}, + {label: 'Etäisyys', required: 'required', type: 'number', publicId: "startDistance", weight: 9}, + {label: 'Osa', required: 'required', type: 'number', publicId: "endRoadPartNumber", weight: 10}, + {label: 'Etäisyys', required: 'required', type: 'number', publicId: "endDistance", weight: 11} ] }; @@ -268,6 +269,11 @@ var publicId = field.publicId; var hasRoadAddress = hasRoadAddressInfo(selectedLinks); + // If lane has road address info and is cut, use lane's calculated road address start and end m-values + // Multiple lane selection for cut lanes is not supported currently and thus chaining road address values for cut lanes is not needed. + var assetHasRoadAddressAndCut = hasRoadAddress && lanesAssets.isLaneFullLinkLength(asset); + if(assetHasRoadAddressAndCut) lanesAssets.getAddressValuesForCutLane(asset); + switch (publicId) { case "roadNumber": case "roadPartNumber": @@ -276,17 +282,35 @@ } break; case "startAddrMValue": - if (hasRoadAddress) { + if (assetHasRoadAddressAndCut) { + value = asset.startAddrMValue; + } + else if (hasRoadAddress) { roadPartNumber = Math.min.apply(null, _.compact(Property.pickUniqueValues(selectedLinks, 'roadPartNumber'))); value = Math.min.apply(null, Property.chainValuesByPublicIdAndRoadPartNumber(selectedLinks, roadPartNumber, publicId)); } break; case "endAddrMValue": - if (hasRoadAddress) { + if (assetHasRoadAddressAndCut) { + value = asset.endAddrMValue; + } + else if (hasRoadAddress) { roadPartNumber = Math.max.apply(null, _.compact(Property.pickUniqueValues(selectedLinks, 'roadPartNumber'))); value = Math.max.apply(null, Property.chainValuesByPublicIdAndRoadPartNumber(selectedLinks, roadPartNumber, publicId)); } break; + case "addrLenght": + if (assetHasRoadAddressAndCut) { + value = asset.endAddrMValue - asset.startAddrMValue; + } + else if (hasRoadAddress) { + var startRoadPartNumber = Math.min.apply(null, _.compact(Property.pickUniqueValues(selectedLinks, 'roadPartNumber'))); + var endRoadPartNumber = Math.max.apply(null, _.compact(Property.pickUniqueValues(selectedLinks, 'roadPartNumber'))); + var selectionStartAddrM = Math.min.apply(null, Property.chainValuesByPublicIdAndRoadPartNumber(selectedLinks, startRoadPartNumber, "startAddrMValue")); + var selectionEndAddrM = Math.max.apply(null, Property.chainValuesByPublicIdAndRoadPartNumber(selectedLinks, endRoadPartNumber, "endAddrMValue")); + value = selectionEndAddrM - selectionStartAddrM; + } + break; case "administrativeClass": value = administrativeClassValues[_.head(selectedLinks)[publicId]]; break; @@ -751,7 +775,7 @@ var newLaneStructure = function (laneNumber) { var indexOfProperty = _.findIndex(defaultFormStructure.fields, {'publicId': 'lane_code'}); - defaultFormStructure.fields[indexOfProperty] = {label: 'Kaista', type: 'read_only_number', publicId: "lane_code", defaultValue: laneNumber, weight: 11}; + defaultFormStructure.fields[indexOfProperty] = {label: 'Kaista', type: 'read_only_number', publicId: "lane_code", defaultValue: laneNumber, weight: 12}; currentFormStructure = defaultFormStructure; }; diff --git a/digiroad2-api-oth/src/main/scala/fi/liikennevirasto/digiroad2/Digiroad2Api.scala b/digiroad2-api-oth/src/main/scala/fi/liikennevirasto/digiroad2/Digiroad2Api.scala index 4db9f175b1..24f273f3d2 100644 --- a/digiroad2-api-oth/src/main/scala/fi/liikennevirasto/digiroad2/Digiroad2Api.scala +++ b/digiroad2-api-oth/src/main/scala/fi/liikennevirasto/digiroad2/Digiroad2Api.scala @@ -1145,6 +1145,7 @@ class Digiroad2Api(val roadLinkService: RoadLinkService, "track" -> lane.attributes.get("TRACK"), "startAddrMValue" -> lane.attributes.get("START_ADDR"), "endAddrMValue" -> lane.attributes.get("END_ADDR"), + "roadAddressSideCode" -> lane.attributes.get("SIDECODE"), "administrativeClass" -> lane.administrativeClass.value, "linkType" -> lane.attributes.getOrElse("linkType", 99), "constructionType" -> lane.attributes.getOrElse("constructionType", 99) diff --git a/digiroad2-api-oth/src/main/scala/fi/liikennevirasto/digiroad2/IntegrationApi.scala b/digiroad2-api-oth/src/main/scala/fi/liikennevirasto/digiroad2/IntegrationApi.scala index 31bcfcd979..eacfe8ff54 100644 --- a/digiroad2-api-oth/src/main/scala/fi/liikennevirasto/digiroad2/IntegrationApi.scala +++ b/digiroad2-api-oth/src/main/scala/fi/liikennevirasto/digiroad2/IntegrationApi.scala @@ -23,6 +23,7 @@ class IntegrationApi(val massTransitStopService: MassTransitStopService, implici protected val applicationDescription = "Integration API " protected implicit val jsonFormats: Formats = DefaultFormats val apiId = "integration-api" + val defaultDecimalPrecision = 3 case class AssetTimeStamps(created: Modification, modified: Modification) extends TimeStamps @@ -31,7 +32,7 @@ class IntegrationApi(val massTransitStopService: MassTransitStopService, implici response.setHeader("Access-Control-Allow-Methods", "OPTIONS,POST,GET"); response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers")); } - + def extractModificationTime(timeStamps: TimeStamps): (String, String) = { "muokattu_viimeksi" -> timeStamps.modified.modificationTime.map(DateTimePropertyFormat.print(_)) @@ -45,6 +46,19 @@ class IntegrationApi(val massTransitStopService: MassTransitStopService, implici .getOrElse("")) } + def doubleToDefaultPrecision(value: Double): Double = { + BigDecimal(value).setScale(defaultDecimalPrecision, BigDecimal.RoundingMode.HALF_UP).toDouble + } + + def geometryToDefaultPrecision(geometry: Seq[Point]): Seq[Point] = { + geometry.map(point => { + val x = doubleToDefaultPrecision(point.x) + val y = doubleToDefaultPrecision(point.y) + val z = doubleToDefaultPrecision(point.z) + Point(x,y,z) + }) + } + private def toGeoJSON(input: Iterable[PersistedMassTransitStop]): Map[String, Any] = { def extractPropertyValue(key: String, properties: Seq[Property], transformation: (Seq[String] => Any), mapName: Option[String] = None): (String, Any) = { val values: Seq[String] = properties.filter { property => property.publicId == key }.flatMap { property => @@ -162,13 +176,15 @@ class IntegrationApi(val massTransitStopService: MassTransitStopService, implici def speedLimitsToApi(speedLimits: Seq[SpeedLimit]): Seq[Map[String, Any]] = { speedLimits.map { speedLimit => + val defaultPrecisionGeometry = geometryToDefaultPrecision(speedLimit.geometry) + Map("id" -> speedLimit.id, "sideCode" -> speedLimit.sideCode.value, - "points" -> speedLimit.geometry, - geometryWKTForLinearAssets(speedLimit.geometry), + "points" -> defaultPrecisionGeometry, + geometryWKTForLinearAssets(defaultPrecisionGeometry), "value" -> speedLimit.value.fold(0)(_.value), - "startMeasure" -> speedLimit.startMeasure, - "endMeasure" -> speedLimit.endMeasure, + "startMeasure" -> doubleToDefaultPrecision(speedLimit.startMeasure), + "endMeasure" -> doubleToDefaultPrecision(speedLimit.endMeasure), "linkId" -> speedLimit.linkId, latestModificationTime(speedLimit.createdDateTime, speedLimit.modifiedDateTime), lastModifiedBy(speedLimit.createdBy, speedLimit.modifiedBy), @@ -179,13 +195,14 @@ class IntegrationApi(val massTransitStopService: MassTransitStopService, implici def speedLimitsChangesToApi(since: DateTime, speedLimits: Seq[ChangedSpeedLimit]) = { speedLimits.map { case ChangedSpeedLimit(speedLimit, link) => + val defaultPrecisionGeometry = geometryToDefaultPrecision(speedLimit.geometry) Map("id" -> speedLimit.id, "sideCode" -> speedLimit.sideCode.value, - "points" -> speedLimit.geometry, - geometryWKTForLinearAssets(speedLimit.geometry), + "points" -> defaultPrecisionGeometry, + geometryWKTForLinearAssets(defaultPrecisionGeometry), "value" -> speedLimit.value.fold(0)(_.value), - "startMeasure" -> speedLimit.startMeasure, - "endMeasure" -> speedLimit.endMeasure, + "startMeasure" -> doubleToDefaultPrecision(speedLimit.startMeasure), + "endMeasure" -> doubleToDefaultPrecision(speedLimit.endMeasure), "linkId" -> speedLimit.linkId, latestModificationTime(speedLimit.createdDateTime, speedLimit.modifiedDateTime), lastModifiedBy(speedLimit.createdBy, speedLimit.modifiedBy), @@ -324,14 +341,15 @@ class IntegrationApi(val massTransitStopService: MassTransitStopService, implici val linearAssets: Seq[PieceWiseLinearAsset] = getLinearAssetService(typeId).getByMunicipality(typeId, municipalityNumber).filterNot(asset => isUnknown(asset) || isSuggested(asset)) linearAssets.map { asset => + val defaultPrecisionGeometry = geometryToDefaultPrecision(asset.geometry) Map("id" -> asset.id, - "points" -> asset.geometry, - geometryWKTForLinearAssets(asset.geometry), + "points" -> defaultPrecisionGeometry, + geometryWKTForLinearAssets(defaultPrecisionGeometry), "value" -> valueToApi(asset.value), "side_code" -> asset.sideCode.value, "linkId" -> asset.linkId, - "startMeasure" -> asset.startMeasure, - "endMeasure" -> asset.endMeasure, + "startMeasure" -> doubleToDefaultPrecision(asset.startMeasure), + "endMeasure" -> doubleToDefaultPrecision(asset.endMeasure), latestModificationTime(asset.createdDateTime, asset.modifiedDateTime), lastModifiedBy(asset.createdBy, asset.modifiedBy), "linkSource" -> asset.linkSource.value @@ -340,13 +358,14 @@ class IntegrationApi(val massTransitStopService: MassTransitStopService, implici } def defaultMultiValueLinearAssetsMap(linearAsset: PieceWiseLinearAsset): Map[String, Any] = { + val defaultPrecisionGeometry = geometryToDefaultPrecision(linearAsset.geometry) Map("id" -> linearAsset.id, - "points" -> linearAsset.geometry, - geometryWKTForLinearAssets(linearAsset.geometry), + "points" -> defaultPrecisionGeometry, + geometryWKTForLinearAssets(defaultPrecisionGeometry), "side_code" -> linearAsset.sideCode.value, "linkId" -> linearAsset.linkId, - "startMeasure" -> linearAsset.startMeasure, - "endMeasure" -> linearAsset.endMeasure, + "startMeasure" -> doubleToDefaultPrecision(linearAsset.startMeasure), + "endMeasure" -> doubleToDefaultPrecision(linearAsset.endMeasure), latestModificationTime(linearAsset.createdDateTime, linearAsset.modifiedDateTime), lastModifiedBy(linearAsset.createdBy, linearAsset.modifiedBy), "linkSource" -> linearAsset.linkSource.value @@ -553,11 +572,13 @@ class IntegrationApi(val massTransitStopService: MassTransitStopService, implici def pedestrianCrossingsToApi(crossings: Seq[PedestrianCrossing]): Seq[Map[String, Any]] = { crossings.filterNot(x => x.floating | isSuggested(x)).map { pedestrianCrossing => + val longitude = doubleToDefaultPrecision(pedestrianCrossing.lon) + val latitude = doubleToDefaultPrecision(pedestrianCrossing.lat) Map("id" -> pedestrianCrossing.id, - "point" -> Point(pedestrianCrossing.lon, pedestrianCrossing.lat), - geometryWKTForPoints(pedestrianCrossing.lon, pedestrianCrossing.lat), + "point" -> Point(longitude, latitude), + geometryWKTForPoints(longitude, latitude), "linkId" -> pedestrianCrossing.linkId, - "m_value" -> pedestrianCrossing.mValue, + "m_value" -> doubleToDefaultPrecision(pedestrianCrossing.mValue), latestModificationTime(pedestrianCrossing.createdAt, pedestrianCrossing.modifiedAt), lastModifiedBy(pedestrianCrossing.createdBy, pedestrianCrossing.modifiedBy), "linkSource" -> pedestrianCrossing.linkSource.value) @@ -574,6 +595,8 @@ class IntegrationApi(val massTransitStopService: MassTransitStopService, implici } trafficLights.filterNot(x => x.floating | isSuggested(x)).map { trafficLight => + val longitude = doubleToDefaultPrecision(trafficLight.lon) + val latitude = doubleToDefaultPrecision(trafficLight.lat) Map("id" -> trafficLight.id, "trafficLights" -> trafficLight.propertyData.groupBy(_.groupedId).map { case (_, properties) => Map( @@ -593,10 +616,10 @@ class IntegrationApi(val massTransitStopService: MassTransitStopService, implici "kunta_id" -> getProperty( properties, "trafficLight_municipality_id"), "tila" -> getProperty( properties, "trafficLight_state") )}, - "point" -> Point(trafficLight.lon, trafficLight.lat), - geometryWKTForPoints(trafficLight.lon, trafficLight.lat), + "point" -> Point(longitude, latitude), + geometryWKTForPoints(longitude, latitude), "linkId" -> trafficLight.linkId, - "m_value" -> trafficLight.mValue, + "m_value" -> doubleToDefaultPrecision(trafficLight.mValue), latestModificationTime(trafficLight.createdAt, trafficLight.modifiedAt), lastModifiedBy(trafficLight.createdBy, trafficLight.modifiedBy), "linkSource" -> trafficLight.linkSource.value) @@ -605,11 +628,13 @@ class IntegrationApi(val massTransitStopService: MassTransitStopService, implici def directionalTrafficSignsToApi(directionalTrafficSign: Seq[DirectionalTrafficSign]): Seq[Map[String, Any]] = { directionalTrafficSign.filterNot(x => x.floating | isSuggested(x)).map { directionalTrafficSign => + val longitude = doubleToDefaultPrecision(directionalTrafficSign.lon) + val latitude = doubleToDefaultPrecision(directionalTrafficSign.lat) Map("id" -> directionalTrafficSign.id, - "point" -> Point(directionalTrafficSign.lon, directionalTrafficSign.lat), - geometryWKTForPoints(directionalTrafficSign.lon, directionalTrafficSign.lat), + "point" -> Point(longitude, latitude), + geometryWKTForPoints(longitude, latitude), "linkId" -> directionalTrafficSign.linkId, - "m_value" -> directionalTrafficSign.mValue, + "m_value" -> doubleToDefaultPrecision(directionalTrafficSign.mValue), "bearing" -> GeometryUtils.calculateActualBearing( directionalTrafficSign.validityDirection,directionalTrafficSign.bearing), "side_code" -> directionalTrafficSign.validityDirection, "text" -> directionalTrafficSign.propertyData.find(_.publicId == "opastustaulun_teksti").get.values.map(_.asInstanceOf[PropertyValue]).headOption.get.propertyValue.split("\n").toSeq, @@ -664,7 +689,7 @@ class IntegrationApi(val massTransitStopService: MassTransitStopService, implici if (geometry.nonEmpty) { val segments = geometry.zip(geometry.tail) - val runningSum = segments.scanLeft(0.0)((current, points) => current + points._1.distance2DTo(points._2)) + val runningSum = segments.scanLeft(0.0)((current, points) => current + points._1.distance2DTo(points._2)).map(doubleToDefaultPrecision) val mValuedGeometry = geometry.zip(runningSum.toList) val wktString = mValuedGeometry.map { case (p, newM) => p.x +" " + p.y + " " + p.z + " " + newM @@ -682,11 +707,13 @@ class IntegrationApi(val massTransitStopService: MassTransitStopService, implici def railwayCrossingsToApi(crossings: Seq[RailwayCrossing]): Seq[Map[String, Any]] = { crossings.filterNot(x => x.floating | isSuggested(x)).map { railwayCrossing => + val longitude = doubleToDefaultPrecision(railwayCrossing.lon) + val latitude = doubleToDefaultPrecision(railwayCrossing.lat) Map("id" -> railwayCrossing.id, - "point" -> Point(railwayCrossing.lon, railwayCrossing.lat), - geometryWKTForPoints(railwayCrossing.lon, railwayCrossing.lat), + "point" -> Point(longitude, latitude), + geometryWKTForPoints(longitude, latitude), "linkId" -> railwayCrossing.linkId, - "m_value" -> railwayCrossing.mValue, + "m_value" -> doubleToDefaultPrecision(railwayCrossing.mValue), "safetyEquipment" -> railwayCrossingService.getProperty(railwayCrossing.propertyData, "turvavarustus").map(_.propertyValue).getOrElse(""), "name" -> railwayCrossingService.getProperty(railwayCrossing.propertyData, "rautatien_tasoristeyksen_nimi").map(_.propertyValue).getOrElse(""), "railwayCrossingId" -> railwayCrossingService.getProperty(railwayCrossing.propertyData, "tasoristeystunnus").map(_.propertyValue).getOrElse(""), @@ -698,11 +725,13 @@ class IntegrationApi(val massTransitStopService: MassTransitStopService, implici def obstaclesToApi(obstacles: Seq[Obstacle]): Seq[Map[String, Any]] = { obstacles.filterNot(x => x.floating | isSuggested(x)).map { obstacle => + val longitude = doubleToDefaultPrecision(obstacle.lon) + val latitude = doubleToDefaultPrecision(obstacle.lat) Map("id" -> obstacle.id, - "point" -> Point(obstacle.lon, obstacle.lat), - geometryWKTForPoints(obstacle.lon, obstacle.lat), + "point" -> Point(longitude, latitude), + geometryWKTForPoints(longitude, latitude), "linkId" -> obstacle.linkId, - "m_value" -> obstacle.mValue, + "m_value" -> doubleToDefaultPrecision(obstacle.mValue), "obstacle_type" -> obstacleService.getProperty(obstacle.propertyData, "esterakennelma").map(_.propertyValue).getOrElse(""), latestModificationTime(obstacle.createdAt, obstacle.modifiedAt), lastModifiedBy(obstacle.createdBy, obstacle.modifiedBy), @@ -728,9 +757,11 @@ class IntegrationApi(val massTransitStopService: MassTransitStopService, implici def servicePointsToApi(servicePoints: Set[ServicePoint]) = { servicePoints.map { asset => + val longitude = doubleToDefaultPrecision(asset.lon) + val latitude = doubleToDefaultPrecision(asset.lat) Map("id" -> asset.id, - "point" -> Point(asset.lon, asset.lat), - geometryWKTForPoints(asset.lon, asset.lat), + "point" -> Point(longitude, latitude), + geometryWKTForPoints(longitude, latitude), "services" -> asset.services, latestModificationTime(asset.createdAt, asset.modifiedAt), lastModifiedBy(asset.createdBy, asset.modifiedBy)) @@ -739,12 +770,14 @@ class IntegrationApi(val massTransitStopService: MassTransitStopService, implici def trWeightLimitationsToApi(weightLimits: Seq[WeightLimit]): Seq[Map[String, Any]] = { weightLimits.filterNot(_.floating).map { weightLimit => + val longitude = doubleToDefaultPrecision(weightLimit.lon) + val latitude = doubleToDefaultPrecision(weightLimit.lat) Map("id" -> weightLimit.id, "linkId" -> weightLimit.linkId, - "point" -> Point(weightLimit.lon, weightLimit.lat), - geometryWKTForPoints(weightLimit.lon, weightLimit.lat), - "m_value" -> weightLimit.mValue, - "value" -> weightLimit.limit, + "point" -> Point(longitude, latitude), + geometryWKTForPoints(longitude, latitude), + "m_value" -> doubleToDefaultPrecision(weightLimit.mValue), + "value" -> doubleToDefaultPrecision(weightLimit.limit), latestModificationTime(weightLimit.createdAt, weightLimit.modifiedAt), lastModifiedBy(weightLimit.createdBy, weightLimit.modifiedBy), "linkSource" -> weightLimit.linkSource.value) @@ -753,12 +786,14 @@ class IntegrationApi(val massTransitStopService: MassTransitStopService, implici def trHeightLimitsToApi(heightLimits: Seq[HeightLimit]): Seq[Map[String, Any]] = { heightLimits.filterNot(_.floating).map { heightLimit => + val longitude = doubleToDefaultPrecision(heightLimit.lon) + val latitude = doubleToDefaultPrecision(heightLimit.lat) Map("id" -> heightLimit.id, "linkId" -> heightLimit.linkId, - "point" -> Point(heightLimit.lon, heightLimit.lat), - geometryWKTForPoints(heightLimit.lon, heightLimit.lat), - "m_value" -> heightLimit.mValue, - "value" -> heightLimit.limit, + "point" -> Point(longitude, latitude), + geometryWKTForPoints(longitude, latitude), + "m_value" -> doubleToDefaultPrecision(heightLimit.mValue), + "value" -> doubleToDefaultPrecision(heightLimit.limit), latestModificationTime(heightLimit.createdAt, heightLimit.modifiedAt), lastModifiedBy(heightLimit.createdBy, heightLimit.modifiedBy), "linkSource" -> heightLimit.linkSource.value) @@ -767,12 +802,14 @@ class IntegrationApi(val massTransitStopService: MassTransitStopService, implici def trWidthLimitsToApi(widthLimits: Seq[WidthLimit]): Seq[Map[String, Any]] = { widthLimits.filterNot(_.floating).map { widthLimit => + val longitude = doubleToDefaultPrecision(widthLimit.lon) + val latitude = doubleToDefaultPrecision(widthLimit.lat) Map("id" -> widthLimit.id, "linkId" -> widthLimit.linkId, - "point" -> Point(widthLimit.lon, widthLimit.lat), - geometryWKTForPoints(widthLimit.lon, widthLimit.lat), - "m_value" -> widthLimit.mValue, - "value" -> widthLimit.limit, + "point" -> Point(longitude, latitude), + geometryWKTForPoints(longitude, latitude), + "m_value" -> doubleToDefaultPrecision(widthLimit.mValue), + "value" -> doubleToDefaultPrecision(widthLimit.limit), "reason" -> widthLimit.reason.value, latestModificationTime(widthLimit.createdAt, widthLimit.modifiedAt), lastModifiedBy(widthLimit.createdBy, widthLimit.modifiedBy), @@ -798,11 +835,13 @@ class IntegrationApi(val massTransitStopService: MassTransitStopService, implici } trafficSigns.filterNot(x => x.floating | isSuggested(x)).map{ trafficSign => + val longitude = doubleToDefaultPrecision(trafficSign.lon) + val latitude = doubleToDefaultPrecision(trafficSign.lat) Map( "id" -> trafficSign.id, - "point" -> Point(trafficSign.lon, trafficSign.lat), - geometryWKTForPoints(trafficSign.lon, trafficSign.lat), + "point" -> Point(longitude, latitude), + geometryWKTForPoints(longitude, latitude), "linkId" -> trafficSign.linkId, - "m_value" -> trafficSign.mValue, + "m_value" -> doubleToDefaultPrecision(trafficSign.mValue), latestModificationTime(trafficSign.createdAt, trafficSign.modifiedAt), lastModifiedBy(trafficSign.createdBy, trafficSign.modifiedBy), "linkSource" -> trafficSign.linkSource.value, diff --git a/digiroad2-geo/src/main/scala/fi/liikennevirasto/digiroad2/GeometryUtils.scala b/digiroad2-geo/src/main/scala/fi/liikennevirasto/digiroad2/GeometryUtils.scala index 8b363a52f8..8995715959 100644 --- a/digiroad2-geo/src/main/scala/fi/liikennevirasto/digiroad2/GeometryUtils.scala +++ b/digiroad2-geo/src/main/scala/fi/liikennevirasto/digiroad2/GeometryUtils.scala @@ -1,11 +1,12 @@ package fi.liikennevirasto.digiroad2 -import fi.liikennevirasto.digiroad2.linearasset.PolyLine +import fi.liikennevirasto.digiroad2.linearasset.{PolyLine, RoadLink} object GeometryUtils { // Default value of minimum distance where locations are considered to be same final private val DefaultEpsilon = 0.01 + final private val adjustmentTolerance = 2.0 def getDefaultEpsilon(): Double = { DefaultEpsilon @@ -17,6 +18,25 @@ object GeometryUtils { else false } + // Use roadLink measures and geometry if measures are within tolerance from road link end points, + // ensures connected geometry between assets in IntegrationApi responses + // TODO Possibly not needed after fillTopology methods are fixed to adjust and save all m-value deviations + def useRoadLinkMeasuresIfCloseEnough(startMeasure: Double, endMeasure: Double, roadLink: RoadLink): (Double, Double, Seq[Point]) = { + val startMeasureWithinTolerance = areMeasuresCloseEnough(startMeasure, 0.0, adjustmentTolerance) + val endMeasureWithinTolerance = areMeasuresCloseEnough(endMeasure, roadLink.length, adjustmentTolerance) + + (startMeasureWithinTolerance, endMeasureWithinTolerance) match { + // Asset covers whole road link, and thus shares geometry with road link + case (true, true) => (0.0, roadLink.length, roadLink.geometry) + // Asset end measure was close enough to road link length, calculate new geometry with end measure being road link length + case (false, true) => (startMeasure, roadLink.length, truncateGeometry3D(roadLink.geometry, startMeasure, roadLink.length)) + // Asset start measure was close enough to road link start (0.0), calculate new geometry with start measure being 0.0 + case (true, false) => (0.0, endMeasure, truncateGeometry3D(roadLink.geometry, 0.0, endMeasure)) + // Asset start and end measures are not close enough to road link end points, use original measures and geometry + case (false, false) => (startMeasure, endMeasure, truncateGeometry3D(roadLink.geometry, startMeasure, endMeasure)) + } + } + def geometryEndpoints(geometry: Seq[Point]): (Point, Point) = { val firstPoint: Point = geometry.head val lastPoint: Point = geometry.last diff --git a/digiroad2-oracle/src/main/resources/clear-db.sql b/digiroad2-oracle/src/main/resources/clear-db.sql index 499913841b..f0a11e599c 100644 --- a/digiroad2-oracle/src/main/resources/clear-db.sql +++ b/digiroad2-oracle/src/main/resources/clear-db.sql @@ -83,6 +83,7 @@ drop table if exists roadlinkex cascade; drop table if exists kgv_roadlink cascade; drop table if exists qgis_roadlinkex cascade; drop table if exists lane_work_list cascade; +drop table if exists change_table cascade; drop sequence if exists grouped_id_seq cascade; drop sequence if exists lrm_position_primary_key_seq cascade; drop sequence if exists manoeuvre_id_seq cascade; diff --git a/digiroad2-oracle/src/main/resources/db/migration/V1_22__add_expired_date_constraint.sql b/digiroad2-oracle/src/main/resources/db/migration/V1_22__add_expired_date_constraint.sql new file mode 100644 index 0000000000..c82751ab1e --- /dev/null +++ b/digiroad2-oracle/src/main/resources/db/migration/V1_22__add_expired_date_constraint.sql @@ -0,0 +1 @@ +ALTER TABLE kgv_roadlink ADD CONSTRAINT expired_date_constraint CHECK (expired_date <= current_timestamp); \ No newline at end of file diff --git a/digiroad2-oracle/src/main/resources/db/migration/V1_23__new_change_table.sql b/digiroad2-oracle/src/main/resources/db/migration/V1_23__new_change_table.sql new file mode 100644 index 0000000000..c46dfb2700 --- /dev/null +++ b/digiroad2-oracle/src/main/resources/db/migration/V1_23__new_change_table.sql @@ -0,0 +1,18 @@ +CREATE TABLE IF NOT EXISTS change_table ( + id numeric(38,0) primary key, + edit_date timestamp not null, + edit_by varchar not null, + change_type varchar not null, + asset_type_id int4, + asset_id numeric not null, + asset_geometry geometry, + start_m_value numeric, + end_m_value numeric, + value varchar, + value_type varchar not null, + link_id varchar, + link_geometry geometry(linestringzm,3067), + link_type int4, + link_length numeric, + link_functional_class int4 +); \ No newline at end of file diff --git a/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/dao/RoadLinkDAO.scala b/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/dao/RoadLinkDAO.scala index 5ed351d35a..c3e688a053 100644 --- a/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/dao/RoadLinkDAO.scala +++ b/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/dao/RoadLinkDAO.scala @@ -302,6 +302,10 @@ class RoadLinkDAO { def fetchWalkwaysByBoundsAndMunicipalities(bounds: BoundingRectangle, municipalities: Set[Int]): Seq[RoadLinkFetched] = { getByMunicipalitiesAndBounds(bounds, municipalities, Some(withMtkClassFilter(Set(12314)))) } + + def fetchExpiredRoadLinks(): Seq[RoadLinkFetched] = { + getExpiredRoadLinks() + } /** * Calls db operation to fetch roadlinks with given filter. @@ -312,20 +316,32 @@ class RoadLinkDAO { getLinksWithFilter(filter(values)).asInstanceOf[Seq[T]] } else Seq.empty[T] } - - protected def getLinksWithFilter(filter: String): Seq[RoadLinkFetched] = { - LogUtils.time(logger,"TEST LOG Getting roadlinks" ){ - sql"""select linkid, mtkid, mtkhereflip, municipalitycode, shape, adminclass, directiontype, mtkclass, roadname_fi, + + protected def getLinksWithFilter(filter: String): Seq[RoadLinkFetched] = { + LogUtils.time(logger,"TEST LOG Getting roadlinks" ){ + sql"""select linkid, mtkid, mtkhereflip, municipalitycode, shape, adminclass, directiontype, mtkclass, roadname_fi, roadname_se, roadnamesme, roadnamesmn, roadnamesms, roadnumber, roadpartnumber, constructiontype, verticallevel, horizontalaccuracy, verticalaccuracy, created_date, last_edited_date, from_left, to_left, from_right, to_right, surfacetype, geometrylength from kgv_roadlink - where #$filter and constructiontype in (${ConstructionType.InUse.value}, + where #$filter + and expired_date is null + and constructiontype in (${ConstructionType.InUse.value}, ${ConstructionType.UnderConstruction.value}, ${ConstructionType.Planned.value}, ${ConstructionType.TemporarilyOutOfUse.value}) """.as[RoadLinkFetched].list - } + } + } + + protected def getExpiredRoadLinks(): Seq[RoadLinkFetched] = { + sql"""select linkid, mtkid, mtkhereflip, municipalitycode, shape, adminclass, directiontype, mtkclass, roadname_fi, + roadname_se, roadnamesme, roadnamesmn, roadnamesms, roadnumber, roadpartnumber, constructiontype, verticallevel, horizontalaccuracy, + verticalaccuracy, created_date, last_edited_date, from_left, to_left, from_right, to_right, + surfacetype, geometrylength + from kgv_roadlink + where expired_date is not null + """.as[RoadLinkFetched].list } private def getByMunicipalitiesAndBounds(bounds: BoundingRectangle, municipalities: Set[Int], @@ -365,6 +381,7 @@ class RoadLinkDAO { sql"""select linkid from kgv_roadlink where #$polygonFilter + and expired_date is null """.as[String].list } } diff --git a/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/RoadAddressService.scala b/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/RoadAddressService.scala index f3c3c13fe8..0289de3a42 100644 --- a/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/RoadAddressService.scala +++ b/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/RoadAddressService.scala @@ -10,7 +10,7 @@ import fi.liikennevirasto.digiroad2.dao.RoadLinkOverrideDAO import fi.liikennevirasto.digiroad2.lane.PieceWiseLane import fi.liikennevirasto.digiroad2.linearasset.{PieceWiseLinearAsset, RoadLink, RoadLinkLike, SpeedLimit} import fi.liikennevirasto.digiroad2.postgis.PostGISDatabase -import fi.liikennevirasto.digiroad2.util.{ClientUtils, Track} +import fi.liikennevirasto.digiroad2.util.{ClientUtils, LogUtils, Track} import fi.liikennevirasto.digiroad2.{DigiroadEventBus, GeometryUtils, MassLimitationAsset, Point} import org.apache.http.conn.HttpHostConnectException import org.joda.time.DateTime @@ -129,8 +129,10 @@ class RoadAddressService(viiteClient: SearchViiteClient ) { * @return */ def getAllByLinkIds(linkIds: Seq[String]): Seq[RoadAddressForLink] = { - ClientUtils.retry(2){ - viiteClient.fetchAllByLinkIds(linkIds) + ClientUtils.retry(2) { + LogUtils.time(logger,"TEST LOG Retrieve road address by links"){ + viiteClient.fetchAllByLinkIds(linkIds) + } } } @@ -142,6 +144,7 @@ class RoadAddressService(viiteClient: SearchViiteClient ) { */ def roadLinkWithRoadAddress(roadLinks: Seq[RoadLink]): Seq[RoadLink] = { try { + val roadAddressLinks = getAllByLinkIds(roadLinks.map(_.linkId)) val addressData = groupRoadAddress(roadAddressLinks).map(a => (a.linkId, a)).toMap logger.info(s"Fetched ${roadAddressLinks.size} road address of ${roadLinks.size} road links.") diff --git a/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/linearasset/LinearAssetService.scala b/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/linearasset/LinearAssetService.scala index fd39b426c6..a5bc6496a2 100644 --- a/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/linearasset/LinearAssetService.scala +++ b/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/linearasset/LinearAssetService.scala @@ -159,7 +159,12 @@ trait LinearAssetOperations { */ def getByMunicipality(typeId: Int, municipality: Int): Seq[PieceWiseLinearAsset] = { val roadLinks = roadLinkService.getRoadLinksWithComplementaryByMunicipalityUsingCache(municipality) - getByRoadLinks(typeId, roadLinks, adjust = false) + val linearAssets = getByRoadLinks(typeId, roadLinks, adjust = false) + linearAssets.map(asset => { + val roadLink = roadLinks.find(_.linkId == asset.linkId).get + val (startMeasure, endMeasure, geometry) = GeometryUtils.useRoadLinkMeasuresIfCloseEnough(asset.startMeasure, asset.endMeasure, roadLink) + asset.copy(startMeasure = startMeasure, endMeasure = endMeasure, geometry = geometry) + }) } def getByMunicipalityAndRoadLinks(typeId: Int, municipality: Int): Seq[(PieceWiseLinearAsset, RoadLink)] = { diff --git a/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/linearasset/SpeedLimitService.scala b/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/linearasset/SpeedLimitService.scala index 50c00aa6cc..39381166c7 100644 --- a/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/linearasset/SpeedLimitService.scala +++ b/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/linearasset/SpeedLimitService.scala @@ -117,7 +117,12 @@ class SpeedLimitService(eventbus: DigiroadEventBus, roadLinkService: RoadLinkSer def get(municipality: Int): Seq[SpeedLimit] = { val (roadLinks, changes) = roadLinkService.getRoadLinksWithComplementaryAndChanges(municipality) withDynTransaction { - getByRoadLinks(roadLinks, changes, roadFilterFunction = {roadLinkFilter: RoadLink => roadLinkFilter.isCarRoadOrCyclePedestrianPath}, adjust = false)._1 + val speedLimits = getByRoadLinks(roadLinks, changes, roadFilterFunction = {roadLinkFilter: RoadLink => roadLinkFilter.isCarRoadOrCyclePedestrianPath}, adjust = false)._1 + speedLimits.map(speedLimit => { + val roadLink = roadLinks.find(_.linkId == speedLimit.linkId).get + val (startMeasure, endMeasure, geometry) = GeometryUtils.useRoadLinkMeasuresIfCloseEnough(speedLimit.startMeasure, speedLimit.endMeasure, roadLink) + speedLimit.copy(startMeasure = startMeasure, endMeasure = endMeasure, geometry = geometry) + }) } } diff --git a/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/util/LogUtils.scala b/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/util/LogUtils.scala index 6412f2c626..5b68ee3253 100644 --- a/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/util/LogUtils.scala +++ b/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/util/LogUtils.scala @@ -7,17 +7,31 @@ object LogUtils { def time[R](logger: Logger, operationName: String, noFilter: Boolean = false, url :Option[String] = None)(f: => R): R = { val begin = System.currentTimeMillis() - val result = f - val duration = System.currentTimeMillis() - begin - val urlString = if ( url.isDefined) {s"URL: ${url.get}"}else "" - if (noFilter) { - logger.info(s"$operationName completed in $duration ms and in second ${duration / 1000}, ${urlString}") - } else { - if (duration >= timeLoggingThresholdInMs) { + + try { + val result = f + val duration = System.currentTimeMillis() - begin + val urlString = if (url.isDefined) { + s"URL: ${url.get}" + } else "" + if (noFilter) { logger.info(s"$operationName completed in $duration ms and in second ${duration / 1000}, ${urlString}") + } else { + if (duration >= timeLoggingThresholdInMs) { + logger.info(s"$operationName completed in $duration ms and in second ${duration / 1000}, ${urlString}") + } } + result + } catch { + case e: Exception => + val errorString = e.getStackTrace.find(st => st.getClassName.startsWith("fi.liikennevirasto")) match { + case Some(lineFound) => s"at $lineFound" + case _ => s"" + } + logger.error(s"$operationName failed. Operation run ${e.getClass.getName}: ${e.getMessage} $errorString") + throw e } - result + } } \ No newline at end of file diff --git a/digiroad2-oracle/src/test/scala/fi/liikennevirasto/digiroad2/dao/RoadLinkDAOSpec.scala b/digiroad2-oracle/src/test/scala/fi/liikennevirasto/digiroad2/dao/RoadLinkDAOSpec.scala index 32c7253492..f994af08a9 100644 --- a/digiroad2-oracle/src/test/scala/fi/liikennevirasto/digiroad2/dao/RoadLinkDAOSpec.scala +++ b/digiroad2-oracle/src/test/scala/fi/liikennevirasto/digiroad2/dao/RoadLinkDAOSpec.scala @@ -6,16 +6,17 @@ import fi.liikennevirasto.digiroad2.util.{LinkIdGenerator, TestTransactions} import org.joda.time.DateTime import org.joda.time.DateTime.now import org.scalatest.FunSuite -import org.scalatest.Matchers.{be, convertToAnyShouldWrapper} +import org.scalatest.Matchers.{be, contain, convertToAnyShouldWrapper} import slick.jdbc.StaticQuery.interpolation import slick.driver.JdbcDriver.backend.Database.dynamicSession class RoadLinkDAOSpec extends FunSuite { - + + val dao = new RoadLinkDAO def runWithRollback(test: => Unit): Unit = TestTransactions.runWithRollback()(test) + test("Fetch link where road names has hyphen symbol in middle of word") { runWithRollback { - val dao = new RoadLinkDAO val (linkId1, linkId2) = (LinkIdGenerator.generateRandom(), LinkIdGenerator.generateRandom()) sqlu"""INSERT INTO kgv_roadlink (linkid, municipalitycode, roadname_fi, roadname_se,constructiontype,shape) VALUES($linkId1, 853, 'Tarkk''ampujankatu', 'Skarpskyttegatan',${ConstructionType.InUse.value},'SRID=3067;LINESTRING ZM(385935.666 6671107.833 19.858 0, 386028.217 6671112.363 20.596 92.661)'::geometry)""".execute sqlu"""INSERT INTO kgv_roadlink (linkid, municipalitycode, roadname_fi, roadname_se,constructiontype,shape) VALUES($linkId2, 441, 'Sammalinen', NULL,${ConstructionType.InUse.value},'SRID=3067;LINESTRING ZM(385935.666 6671107.833 19.85 0, 386028.217 6671112.363 20.596 92.661)'::geometry)""".execute @@ -30,11 +31,45 @@ class RoadLinkDAOSpec extends FunSuite { super.extractModifiedDate(createdDate,lastEdited) } } - val dao = new ExposeDao + val exposeDao = new ExposeDao val dateLong = new DateTime(now) - dao.extractModifiedDate(Some(1L),Some(dateLong.getMillis)).get.toString should be(dateLong.toString) - dao.extractModifiedDate(Some(1L),None).get.toString should be(new DateTime(1).toString()) + exposeDao.extractModifiedDate(Some(1L),Some(dateLong.getMillis)).get.toString should be(dateLong.toString) + exposeDao.extractModifiedDate(Some(1L),None).get.toString should be(new DateTime(1).toString()) + } + + test("only valid links are fetched") { + val (linkId1, linkId2, linkId3) = (LinkIdGenerator.generateRandom(), LinkIdGenerator.generateRandom(), LinkIdGenerator.generateRandom()) + runWithRollback { + sqlu"""insert into kgv_roadlink (linkId, municipalitycode, constructiontype, shape) values ($linkId1, 235, ${ConstructionType.InUse.value}, + 'SRID=3067;LINESTRING ZM(385935.666 6671107.833 19.858 0, 386028.217 6671112.363 20.596 92.661)'::geometry)""".execute + sqlu"""insert into kgv_roadlink (linkId, municipalitycode, constructiontype, shape) values ($linkId2, 235, ${ConstructionType.InUse.value}, + 'SRID=3067;LINESTRING ZM(385935.666 6671107.833 19.85 0, 386028.217 6671112.363 20.596 92.661)'::geometry)""".execute + sqlu"""insert into kgv_roadlink (linkId, municipalitycode, constructiontype, shape, expired_date) values ($linkId3, 235, ${ConstructionType.InUse.value}, + 'SRID=3067;LINESTRING ZM(385935.666 6671107.833 19.85 0, 386028.217 6671112.363 20.596 92.661)'::geometry, '2022-05-10 10:52:28.783')""".execute + val validLinks = dao.fetchByLinkIds(Set(linkId1, linkId2, linkId3)) + validLinks.size should be(2) + val validLinkIds = validLinks.map(_.linkId) + validLinkIds should contain(linkId1) + validLinkIds should contain(linkId2) + validLinkIds shouldNot contain(linkId3) + } } - + + test("only expired links are fetched") { + val (linkId1, linkId2, linkId3) = (LinkIdGenerator.generateRandom(), LinkIdGenerator.generateRandom(), LinkIdGenerator.generateRandom()) + runWithRollback { + sqlu"""insert into kgv_roadlink (linkId, municipalitycode, constructiontype, shape) values ($linkId1, 235, ${ConstructionType.InUse.value}, + 'SRID=3067;LINESTRING ZM(385935.666 6671107.833 19.858 0, 386028.217 6671112.363 20.596 92.661)'::geometry)""".execute + sqlu"""insert into kgv_roadlink (linkId, municipalitycode, constructiontype, shape) values ($linkId2, 235, ${ConstructionType.InUse.value}, + 'SRID=3067;LINESTRING ZM(385935.666 6671107.833 19.85 0, 386028.217 6671112.363 20.596 92.661)'::geometry)""".execute + sqlu"""insert into kgv_roadlink (linkId, municipalitycode, constructiontype, shape, expired_date) values ($linkId3, 235, ${ConstructionType.InUse.value}, + 'SRID=3067;LINESTRING ZM(385935.666 6671107.833 19.85 0, 386028.217 6671112.363 20.596 92.661)'::geometry, '2022-05-10 10:52:28.783')""".execute + val expiredLinks = dao.fetchExpiredRoadLinks() + val expiredLinkIds = expiredLinks.map(_.linkId) + expiredLinkIds shouldNot contain (linkId1) + expiredLinkIds shouldNot contain (linkId2) + expiredLinkIds should contain (linkId3) + } + } }