From 4751e5015c6d0d243f81fdc005e61a3ff72d869c Mon Sep 17 00:00:00 2001 From: Juha-Pekka Tulijoki Date: Thu, 24 Mar 2022 08:53:37 +0200 Subject: [PATCH 1/7] DROTH-3109 sidecode filtering to createmultilanes to fix api error --- .../fi/liikennevirasto/digiroad2/service/lane/LaneService.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/lane/LaneService.scala b/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/lane/LaneService.scala index 1a10f9c473..e8ec9a8463 100644 --- a/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/lane/LaneService.scala +++ b/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/lane/LaneService.scala @@ -920,7 +920,7 @@ trait LaneOperations { val laneCodesToModify = updateNewLanes.map { newLane => getLaneCode(newLane).toInt } //Fetch from db the existing lanes - val oldLanes = dao.fetchLanesByLinkIdsAndLaneCode(linkIds.toSeq, laneCodesToModify) + val oldLanes = dao.fetchLanesByLinkIdsAndLaneCode(linkIds.toSeq, laneCodesToModify).filter(_.sideCode == sideCode) val newLanesByLaneCode = updateNewLanes.groupBy(il => getLaneCode(il).toInt) From d943c3e576a43e81fd8e84bf192dd46d84ecf03e Mon Sep 17 00:00:00 2001 From: Juha-Pekka Tulijoki Date: Mon, 25 Apr 2022 09:20:35 +0300 Subject: [PATCH 2/7] DROTH-3109 Functionality for splitting lanes multiple times --- UI/src/controller/laneModellingCollection.js | 5 +- UI/src/model/selectedLaneModelling.js | 58 ++--- UI/src/view/linear_asset/laneModellingForm.js | 21 +- .../view/linear_asset/laneModellingLayer.js | 74 +++++-- .../digiroad2/service/lane/LaneService.scala | 34 ++- .../service/lane/LaneServiceSpec.scala | 202 ++++++++++++++++++ 6 files changed, 321 insertions(+), 73 deletions(-) diff --git a/UI/src/controller/laneModellingCollection.js b/UI/src/controller/laneModellingCollection.js index 5cef0f0679..0a0fc03cf8 100644 --- a/UI/src/controller/laneModellingCollection.js +++ b/UI/src/controller/laneModellingCollection.js @@ -42,7 +42,7 @@ right.points = split.secondSplitVertices; right.startMeasure = split.splitMeasure; - if (self.calculateMeasure(left) < self.calculateMeasure(right)) { + if (left.startMeasure < right.startMeasure) { self.splitLinearAssets.created = left; self.splitLinearAssets.existing = right; } else { @@ -53,9 +53,6 @@ self.splitLinearAssets.created.id = 0; self.splitLinearAssets.existing.id = 0; - self.splitLinearAssets.created.marker = 'A'; - self.splitLinearAssets.existing.marker = 'B'; - self.dirty = true; callback(self.splitLinearAssets); }; diff --git a/UI/src/model/selectedLaneModelling.js b/UI/src/model/selectedLaneModelling.js index 8f62907176..2e5ace0c4f 100644 --- a/UI/src/model/selectedLaneModelling.js +++ b/UI/src/model/selectedLaneModelling.js @@ -74,28 +74,26 @@ return numberOfLanesByLaneCode[key] > 1; }); - var lanesSortedByLaneCode = _.sortBy(lanes, getLaneCodeValue); - - var duplicateLaneCounter = 0; - return _.map(lanesSortedByLaneCode, function (lane) { - if(_.includes(laneCodesToPutMarkers, getLaneCodeValue(lane).toString())) { - if (duplicateLaneCounter === 0){ - lane.marker = 'A'; - duplicateLaneCounter++; - }else{ - lane.marker = 'B'; - duplicateLaneCounter--; - } - } - return lane; + var lanesSortedByEndMeasure = _.sortBy(lanes, function(lane) { + return lane.endMeasure; }); + + var charCount = 0; + for (var i = 0; i < lanesSortedByEndMeasure.length; i++) { + if (_.includes(laneCodesToPutMarkers, getLaneCodeValue(lanesSortedByEndMeasure[i]).toString())) { + lanesSortedByEndMeasure[i].marker = String.fromCharCode(charCount + 65); + charCount += 1; + } + } + + return lanesSortedByEndMeasure; }; //Outer lanes that are expired are to be considered, the other are updates so we need to take those out //Here a outer lane is a lane with lane code that existed in the original but not in the modified configuration function omitIrrelevantExpiredLanes() { var lanesToBeRemovedFromExpire = _.filter(assetsToBeExpired, function (lane) { - return !_.isUndefined(self.getLane(getLaneCodeValue(lane))); + return !self.isOuterLane(getLaneCodeValue(lane)); }); _.forEach(lanesToBeRemovedFromExpire, function (lane) { @@ -103,15 +101,12 @@ }); } - self.splitLinearAsset = function(laneNumber, split) { - collection.splitLinearAsset(self.getLane(laneNumber), split, function(splitLinearAssets) { - if (self.getLane(laneNumber).id === 0) { - self.removeLane(laneNumber); - } else { - self.expireLane(laneNumber); - } - + self.splitLinearAsset = function(laneNumber, split, laneMarker) { + collection.splitLinearAsset(self.getLane(laneNumber, laneMarker), split, function(splitLinearAssets) { + var laneIndex = getLaneIndex(laneNumber, laneMarker); + self.selection.splice(laneIndex,1); self.selection.push(splitLinearAssets.created, splitLinearAssets.existing); + self.selection = giveSplitMarkers(self.selection); self.dirty = true; eventbus.trigger('laneModellingForm: reload'); }); @@ -198,18 +193,25 @@ self.lanesCutAreEqual = function() { var laneNumbers = _.map(self.selection, getLaneCodeValue); - var cuttedLaneNumbers = _.transform(_.countBy(laneNumbers), function(result, count, value) { + var cutLaneNumbers = _.transform(_.countBy(laneNumbers), function(result, count, value) { if (count > 1) result.push(value); }, []); - return _.some(cuttedLaneNumbers, function (laneNumber){ + return _.some(cutLaneNumbers, function (laneNumber){ var lanes = _.filter(self.selection, function (lane){ return _.find(lane.properties, function (property) { return property.publicId == "lane_code" && _.head(property.values).value == laneNumber; }); }); - - return _.isEqual(lanes[0].properties, lanes[1].properties); + var sortedLanes = _.sortBy(lanes, function (lane) { + return lane.endMeasure; + }); + for (var i = 1; i < sortedLanes.length; i++) { + if (_.isEqual(sortedLanes[i - 1].properties, sortedLanes[i].properties)) { + return true; + } + } + return false; }); }; @@ -382,7 +384,7 @@ //expiredLane could be modified by the user so we need to fetch the original var originalExpiredLane = _.find(lanesFetched, {'id': expiredLane.id}); - if (linksSelected.length > 1) { + if (linksSelected.length > 1 && marker === undefined) { var expiredGroup = collection.getGroup(originalExpiredLane); expiredGroup.forEach(function (lane) { lane.isExpired = true; diff --git a/UI/src/view/linear_asset/laneModellingForm.js b/UI/src/view/linear_asset/laneModellingForm.js index 22ea330750..c5605b186d 100644 --- a/UI/src/view/linear_asset/laneModellingForm.js +++ b/UI/src/view/linear_asset/laneModellingForm.js @@ -443,23 +443,20 @@ }); }); + asset = _.sortBy(asset, function (lane) { + return lane.marker; + }); + var body = createBodyElement(selectedAsset.getCurrentLane()); if(selectedAsset.isSplit()) { - //Render form A - renderFormElements(_.find(asset,{'marker': 'A'}), isReadOnly, 'A', selectedAsset.setValue, selectedAsset.getValue, false, body); - if(!isReadOnly) - renderExpireAndDeleteButtonsElement(selectedAsset, body, 'A'); - - body.find('.form').append('
'); - //Render form B - renderFormElements(_.find(asset,{'marker': 'B'}), isReadOnly, 'B', selectedAsset.setValue, selectedAsset.getValue, false, body); - - if(!isReadOnly) - renderExpireAndDeleteButtonsElement(selectedAsset, body, 'B'); + _.forEach(asset, function (lane) { + renderFormElements(lane, isReadOnly, lane.marker, selectedAsset.setValue, selectedAsset.getValue, false, body); + if(!isReadOnly) renderExpireAndDeleteButtonsElement(selectedAsset, body, lane.marker); + body.find('.form').append('
'); + }); - body.find('.form').append('
'); }else{ renderFormElements(asset[0], isReadOnly, '', selectedAsset.setValue, selectedAsset.getValue, isDisabled, body); diff --git a/UI/src/view/linear_asset/laneModellingLayer.js b/UI/src/view/linear_asset/laneModellingLayer.js index 2d3f49bb9b..384a94a36d 100644 --- a/UI/src/view/linear_asset/laneModellingLayer.js +++ b/UI/src/view/linear_asset/laneModellingLayer.js @@ -17,7 +17,7 @@ var LinearAssetCutter = function(eventListener, vectorLayer) { var scissorFeatures = []; - var CUT_THRESHOLD = 20; + var CUT_THRESHOLD = 5; var vectorSource = vectorLayer.getSource(); var moveTo = function(x, y) { @@ -81,8 +81,8 @@ return property.publicId === "lane_code"; }).values).value; - return !selectedLane.isOuterLane(laneNumber) || laneNumber == 1 || - !_.isUndefined(properties.marker) || properties.selectedLinks.length > 1 || selectedLane.isAddByRoadAddress(); + return !selectedLane.isOuterLane(laneNumber) || laneNumber == 1 || (!_.isUndefined(properties) && + properties.selectedLinks.length > 1 && _.isUndefined(properties.marker)) || selectedLane.isAddByRoadAddress(); }) .map(function(feature) { var closestP = feature.getGeometry().getClosestPoint(point); @@ -100,6 +100,43 @@ .value(); }; + var getLanePieceToCut = function (point) { + + var minSquaredDistanceBetweenPointAndLane = function (lane, point) { + var min_x_dist = Math.abs(lane.points[0].x - point[0]); + var min_y_dist = Math.abs(lane.points[0].y - point[1]); + for(var i=1; i < lane.points.length; i++) { + min_x_dist = Math.min(min_x_dist, Math.abs(lane.points[i].x - point[0])); + min_y_dist = Math.min(min_y_dist, Math.abs(lane.points[i].y - point[1])); + } + return Math.pow(min_x_dist, 2) + Math.pow(min_y_dist, 2); + }; + + var selected = selectedLane.get(); + + selected = _.reject(selected, function (lane) { + var laneNumber = _.head(_.find(lane.properties, function(property){ + return property.publicId === "lane_code"; + }).values).value; + + return !selectedLane.isOuterLane(laneNumber) || laneNumber == 1 || selectedLane.isAddByRoadAddress(); + }); + + selected = _.sortBy(selected, function (lane) { + return minSquaredDistanceBetweenPointAndLane(lane, point); + }); + + var cutLanes = _.reject(selected, function (lane) { + return _.isUndefined(lane.marker); + }); + + if (_.isEmpty(cutLanes)) { + return _.head(selected); + } + + return _.head(cutLanes); + }; + this.updateByPosition = function(mousePoint) { var closestLinearAssetLink = findNearestLinearAssetLink(mousePoint); if (closestLinearAssetLink) { @@ -128,18 +165,14 @@ return _.merge({ splitMeasure: splitMeasure }, splitVertices); }; - var nearest = findNearestLinearAssetLink([mousePoint.x, mousePoint.y]); - - if (_.isUndefined(nearest) || !isWithinCutThreshold(nearest.distance)) { - return; - } + var nearestLinearAsset = getLanePieceToCut([mousePoint.x, mousePoint.y]); + if (_.isUndefined(nearestLinearAsset)) return; - var nearestLinearAsset = nearest.feature.getProperties(); if(authorizationPolicy.formEditModeAccess(nearestLinearAsset)) { - var splitProperties = calculateSplitProperties(laneUtils.offsetByLaneNumber(nearestLinearAsset, false, true), mousePoint); + var splitProperties = calculateSplitProperties(nearestLinearAsset, mousePoint); selectedLane.splitLinearAsset(_.head(_.find(nearestLinearAsset.properties, function(property){ return property.publicId === "lane_code"; - }).values).value, splitProperties); + }).values).value, splitProperties, nearestLinearAsset.marker); remove(); } @@ -296,6 +329,7 @@ }; var drawSplitPoints = function (links) { + function findSplitPoints(links) { var sortedPoints = _.flatMap(links, function (link) { return link.points; @@ -381,7 +415,7 @@ var linearAssets = _.flatten(linearAssetChains); var allButSelected = _.filter(linearAssets, function(asset){ return !_.some(selectedLane.get(), function(selectedAsset){ return selectedAsset.linkId === asset.linkId && selectedAsset.sideCode == asset.sideCode && - selectedAsset.startMeasure === asset.startMeasure && selectedAsset.endMeasure === asset.endMeasure; }) ; + selectedAsset.startMeasure === asset.startMeasure && selectedAsset.endMeasure === asset.endMeasure; }) ; }); me.vectorSource.addFeatures(style.renderFeatures(allButSelected)); var oneWaySignsDraft = getOneWaySigns(allButSelected); @@ -430,17 +464,17 @@ var selectedFeatures = style.renderFeatures(linearAssets, laneNumber); if (assetLabel) { - var currentFeatures = _.filter(me.vectorSource.getFeatures(), function (layerFeature) { - return _.some(selectedFeatures, function (selectedFeature) { - return me.geometryAndValuesEqual(selectedFeature.values_, layerFeature.values_); - }); + var currentFeatures = _.filter(me.vectorSource.getFeatures(), function (layerFeature) { + return _.some(selectedFeatures, function (selectedFeature) { + return me.geometryAndValuesEqual(selectedFeature.values_, layerFeature.values_); }); + }); - _.each(currentFeatures, me.removeFeature); + _.each(currentFeatures, me.removeFeature); - selectedFeatures = selectedFeatures.concat(assetLabel.renderFeaturesByLinearAssets(extractMiddleLinksOfChains([_.map(selectedFeatures, function (feature) { - return feature.getProperties(); - })]), me.uiState.zoomLevel)); + selectedFeatures = selectedFeatures.concat(assetLabel.renderFeaturesByLinearAssets(extractMiddleLinksOfChains([_.map(selectedFeatures, function (feature) { + return feature.getProperties(); + })]), me.uiState.zoomLevel)); } removeOldAssetFeatures(); diff --git a/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/lane/LaneService.scala b/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/lane/LaneService.scala index e8ec9a8463..8bbfd835ae 100644 --- a/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/lane/LaneService.scala +++ b/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/lane/LaneService.scala @@ -579,7 +579,16 @@ trait LaneOperations { val oldLane = allExistingLanes.find(laneAux => laneAux.laneCode == lane.laneCode && laneAux.linkId == linkId) .getOrElse(throw new InvalidParameterException(s"LinkId: $linkId dont have laneCode: ${lane.laneCode} for update!")) - if (linkIds.size == 1 && + if (linkIds.size == 1 && lane.startMeasure == laneToUpdate.startMeasure && lane.startMeasure == laneToUpdate.startMeasure && lane.laneCode == laneToUpdateCode) { + var newLaneID = lane.id + if (isSomePropertyDifferent(lane, laneToUpdate.properties)) { + val persistedLaneToUpdate = PersistedLane(lane.id, linkId, sideCode, laneToUpdateCode, lane.municipalityCode, + lane.startMeasure, lane.endMeasure, Some(username), None, None, None, None, None, false, 0, None, laneToUpdate.properties) + moveToHistory(lane.id, None, false, false, username) + newLaneID = dao.updateEntryLane(persistedLaneToUpdate, username) + } + newLaneID + } else if (linkIds.size == 1 && (oldLane.startMeasure != laneToUpdate.startMeasure || oldLane.endMeasure != laneToUpdate.endMeasure)) { val newLaneID = create(Seq(laneToUpdate), Set(linkId), sideCode, username) moveToHistory(oldLane.id, Some(newLaneID.head), true, true, username) @@ -883,14 +892,14 @@ trait LaneOperations { } //Get multiple lanes in one link - val resultWithMultiLanesInLink = newLanes.filter(_.isExpired != true).foldLeft(resultWithDeleteActions) { + val resultWithMultiLanesInLink = newLanes.filter(lane => lane.isExpired != true && lane.id == 0).foldLeft(resultWithDeleteActions) { (result, newLane) => val newLaneCode: Int = getPropertyValue(newLane.properties, "lane_code").toString.toInt val numberOfOldLanesByCode = allExistingLanes.count(_.laneCode == newLaneCode) val numberOfFutureLanesByCode = newLanes.filter(_.isExpired != true).count { newLane => getLaneCode(newLane).toInt == newLaneCode } - if ((numberOfFutureLanesByCode == 2 && numberOfOldLanesByCode >= 1) || (numberOfFutureLanesByCode == 1 && numberOfOldLanesByCode == 2)) + if ((numberOfFutureLanesByCode >= 2 && numberOfOldLanesByCode >= 1) || (numberOfFutureLanesByCode < numberOfOldLanesByCode)) result.copy(multiLanesOnLink = Set(newLane) ++ result.multiLanesOnLink) else result @@ -916,11 +925,16 @@ trait LaneOperations { } def createMultiLanesOnLink(updateNewLanes: Seq[NewLane], linkIds: Set[Long], sideCode: Int, username: String): Seq[Long] = { + if (updateNewLanes.size == 0) { + return Seq() + } // Get all lane codes from lanes to update val laneCodesToModify = updateNewLanes.map { newLane => getLaneCode(newLane).toInt } - - //Fetch from db the existing lanes - val oldLanes = dao.fetchLanesByLinkIdsAndLaneCode(linkIds.toSeq, laneCodesToModify).filter(_.sideCode == sideCode) + val startMeasureToModify = updateNewLanes.map(newLane => newLane.startMeasure).reduce(_ min _) + val endMeasureToModify = updateNewLanes.map(newLane => newLane.endMeasure).reduce(_ max _) + //Fetch from db the existing lanes that the new lanes will replace + val oldLanes = dao.fetchLanesByLinkIdsAndLaneCode(linkIds.toSeq, laneCodesToModify).filter(lane => lane.sideCode == sideCode + && lane.startMeasure >= startMeasureToModify && lane.endMeasure <= endMeasureToModify) val newLanesByLaneCode = updateNewLanes.groupBy(il => getLaneCode(il).toInt) @@ -929,8 +943,8 @@ trait LaneOperations { val oldLanesByCode = oldLanes.filter(_.laneCode == laneCode) - if (lanesToUpdate.size == 2 && oldLanesByCode.size == 1) { - //When its to create two lanes in one link + if (lanesToUpdate.size > oldLanesByCode.size) { + //When one or more lanes are cut to smaller pieces val newLanesIDs = lanesToUpdate.map { lane => create(Seq(lane), linkIds, sideCode, username).head } @@ -938,7 +952,9 @@ trait LaneOperations { newLanesIDs.foreach { newLane => moveToHistory(oldLanesByCode.head.id, Some(newLane), true, false, username) } - dao.deleteEntryLane(oldLanesByCode.head.id) + oldLanes.foreach {oldLane => + dao.deleteEntryLane(oldLane.id) + } newLanesIDs diff --git a/digiroad2-oracle/src/test/scala/fi/liikennevirasto/digiroad2/service/lane/LaneServiceSpec.scala b/digiroad2-oracle/src/test/scala/fi/liikennevirasto/digiroad2/service/lane/LaneServiceSpec.scala index 061aefc58e..f62c96d58a 100644 --- a/digiroad2-oracle/src/test/scala/fi/liikennevirasto/digiroad2/service/lane/LaneServiceSpec.scala +++ b/digiroad2-oracle/src/test/scala/fi/liikennevirasto/digiroad2/service/lane/LaneServiceSpec.scala @@ -1397,4 +1397,206 @@ class LaneServiceSpec extends LaneTestSupporter { laneTowardsTwoDigit.laneCode should equal(11) laneAgainstTwoDigit.laneCode should equal(21) } + + test("Create three new split lanes") { + runWithRollback { + val mainLane1 = NewLane(0, 0, 500, 745, false, false, lanePropertiesValues1) + val mainLane1Id = ServiceWithDao.create(Seq(mainLane1), Set(100L), 1, usernameTest).head + + val initialLanes = laneDao.fetchLanesByLinkIdsAndLaneCode(Seq(100L), Seq(1, 2), false) + initialLanes.size should be(1) + + val createdMainLane = NewLane(mainLane1Id, 0, 500, 745, false, false, lanePropertiesValues1) + + val subLane2SplitA = NewLane(0, 0, 150, 745, false, false, lanePropertiesValues2) + val subLane2SplitB = NewLane(0, 150, 350, 745, false, false, lanePropertiesValues2) + val subLane2SplitC = NewLane(0, 350, 500, 745, false, false, lanePropertiesValues2) + + ServiceWithDao.processNewLanes(Set(createdMainLane, subLane2SplitA, subLane2SplitB, subLane2SplitC), Set(100L), 1, usernameTest, Seq()) + + val currentLanes = laneDao.fetchLanesByLinkIdsAndLaneCode(Seq(100L), Seq(1, 2), false) + currentLanes.size should be(4) + + val lane1 = currentLanes.filter(_.id == mainLane1Id).head + lane1.id should be(mainLane1Id) + lane1.attributes.foreach { laneProp => + lanePropertiesValues1.contains(laneProp) should be(true) + } + + val splitLanes = currentLanes.filter(_.laneCode == 2).sortBy(_.startMeasure) + splitLanes.size should be(3) + + splitLanes(0).startMeasure should be(0) + splitLanes(0).endMeasure should be(150) + splitLanes(1).startMeasure should be(150) + splitLanes(1).endMeasure should be(350) + splitLanes(2).startMeasure should be(350) + splitLanes(2).endMeasure should be(500) + + splitLanes.foreach { lane => + lane.attributes.foreach { + laneProp => + lanePropertiesValues2.contains(laneProp) should be(true) + } + } + + val expiredLanes = laneHistoryDao.fetchHistoryLanesByLinkIdsAndLaneCode(Seq(100L), Seq(2), true) + expiredLanes.size should be(0) + } + } + + test("Split existing lane in three pieces") { + runWithRollback { + val mainLane1 = NewLane(0, 0, 500, 745, false, false, lanePropertiesValues1) + val subLane2 = NewLane(0, 0, 500, 745, false, false, lanePropertiesValues2) + val mainLane1Id = ServiceWithDao.create(Seq(mainLane1), Set(100L), 1, usernameTest).head + ServiceWithDao.create(Seq(subLane2), Set(100L), 1, usernameTest) + + val initialLanes = laneDao.fetchLanesByLinkIdsAndLaneCode(Seq(100L), Seq(1, 2), false) + initialLanes.size should be(2) + + val createdMainLane = NewLane(mainLane1Id, 0, 500, 745, false, false, lanePropertiesValues1) + + val subLane2SplitA = NewLane(0, 0, 150, 745, false, false, lanePropertiesValues2) + val subLane2SplitB = NewLane(0, 150, 350, 745, false, false, lanePropertiesValues2) + val subLane2SplitC = NewLane(0, 350, 500, 745, false, false, lanePropertiesValues2) + + ServiceWithDao.processNewLanes(Set(createdMainLane, subLane2SplitA, subLane2SplitB, subLane2SplitC), Set(100L), 1, usernameTest, Seq()) + + val currentLanes = laneDao.fetchLanesByLinkIdsAndLaneCode(Seq(100L), Seq(1, 2), false) + currentLanes.size should be(4) + + val lane1 = currentLanes.filter(_.id == mainLane1Id).head + lane1.id should be(mainLane1Id) + lane1.attributes.foreach { laneProp => + lanePropertiesValues1.contains(laneProp) should be(true) + } + + val splitLanes = currentLanes.filter(_.laneCode == 2).sortBy(_.startMeasure) + splitLanes.size should be(3) + + splitLanes(0).startMeasure should be(0) + splitLanes(0).endMeasure should be(150) + splitLanes(1).startMeasure should be(150) + splitLanes(1).endMeasure should be(350) + splitLanes(2).startMeasure should be(350) + splitLanes(2).endMeasure should be(500) + + splitLanes.foreach { lane => + lane.attributes.foreach { + laneProp => + lanePropertiesValues2.contains(laneProp) should be(true) + } + } + + val expiredLanes = laneHistoryDao.fetchHistoryLanesByLinkIdsAndLaneCode(Seq(100L), Seq(2), true) + expiredLanes.size should be(3) + } + } + + test("Change properties of several splits") { + runWithRollback { + val mainLane1 = NewLane(0, 0, 500, 745, false, false, lanePropertiesValues1) + val subLane2SplitA = NewLane(0, 0, 100, 745, false, false, lanePropertiesValues2) + val subLane2SplitB = NewLane(0, 100, 200, 745, false, false, lanePropertiesValues2) + val subLane2SplitC = NewLane(0, 200, 300, 745, false, false, lanePropertiesValues2) + val subLane2SplitD = NewLane(0, 300, 400, 745, false, false, lanePropertiesValues2) + val subLane2SplitE = NewLane(0, 400, 500, 745, false, false, lanePropertiesValues2) + val laneIds = ServiceWithDao.create(Seq(mainLane1, subLane2SplitA, subLane2SplitB, subLane2SplitC, subLane2SplitD, subLane2SplitE), Set(100L), 1, usernameTest) + + val initialLanes = laneDao.fetchLanesByLinkIdsAndLaneCode(Seq(100L), Seq(1, 2), false) + initialLanes.size should be(6) + + val newPropertyValues1 = Seq(LaneProperty("lane_code", Seq(LanePropertyValue(2))), + LaneProperty("lane_type", Seq(LanePropertyValue("5"))), + LaneProperty("start_date", Seq(LanePropertyValue(DateTime.now().toString("dd.MM.yyyy")))) + ) + + val newPropertyValues2 = Seq(LaneProperty("lane_code", Seq(LanePropertyValue(2))), + LaneProperty("lane_type", Seq(LanePropertyValue("13"))), + LaneProperty("start_date", Seq(LanePropertyValue(DateTime.now().toString("dd.MM.yyyy")))) + ) + + val createdMainLane = NewLane(laneIds(0), 0, 500, 745, false, false, lanePropertiesValues1) + val createdSubLane2SplitA = NewLane(laneIds(1), 0, 100, 745, false, false, lanePropertiesValues2) + val createdSubLane2SplitB = NewLane(laneIds(2), 100, 200, 745, false, false, newPropertyValues1) + val createdSubLane2SplitC = NewLane(laneIds(3), 200, 300, 745, false, false, newPropertyValues2) + val createdSubLane2SplitD = NewLane(laneIds(4), 300, 400, 745, false, false, lanePropertiesValues2) + val createdSubLane2SplitE = NewLane(laneIds(5), 400, 500, 745, false, false, newPropertyValues2) + + ServiceWithDao.processNewLanes(Set(createdMainLane, createdSubLane2SplitA, createdSubLane2SplitB, createdSubLane2SplitC, createdSubLane2SplitD, createdSubLane2SplitE), Set(100L), 1, usernameTest, Seq()) + + val currentLanes = laneDao.fetchLanesByLinkIdsAndLaneCode(Seq(100L), Seq(1, 2), false) + currentLanes.size should be(6) + + val splitLanes = currentLanes.filter(_.laneCode == 2).sortBy(_.startMeasure) + + splitLanes(0).attributes.foreach { + laneProp => + lanePropertiesValues2.contains(laneProp) should be(true) + } + + splitLanes(1).attributes.foreach { + laneProp => + newPropertyValues1.contains(laneProp) should be(true) + } + + splitLanes(2).attributes.foreach { + laneProp => + newPropertyValues2.contains(laneProp) should be(true) + } + + splitLanes(3).attributes.foreach { + laneProp => + lanePropertiesValues2.contains(laneProp) should be(true) + } + + splitLanes(4).attributes.foreach { + laneProp => + newPropertyValues2.contains(laneProp) should be(true) + } + + val expiredLanes = laneHistoryDao.fetchHistoryLanesByLinkIdsAndLaneCode(Seq(100L), Seq(2), true) + expiredLanes.size should be(3) + + } + } + + test("Expire several split lanes") { + runWithRollback { + val mainLane1 = NewLane(0, 0, 500, 745, false, false, lanePropertiesValues1) + val subLane2SplitA = NewLane(0, 0, 100, 745, false, false, lanePropertiesValues2) + val subLane2SplitB = NewLane(0, 100, 200, 745, false, false, lanePropertiesValues2) + val subLane2SplitC = NewLane(0, 200, 300, 745, false, false, lanePropertiesValues2) + val subLane2SplitD = NewLane(0, 300, 400, 745, false, false, lanePropertiesValues2) + val subLane2SplitE = NewLane(0, 400, 500, 745, false, false, lanePropertiesValues2) + val laneIds = ServiceWithDao.create(Seq(mainLane1, subLane2SplitA, subLane2SplitB, subLane2SplitC, subLane2SplitD, subLane2SplitE), Set(100L), 1, usernameTest) + + val initialLanes = laneDao.fetchLanesByLinkIdsAndLaneCode(Seq(100L), Seq(1, 2), false) + initialLanes.size should be(6) + + val createdMainLane = NewLane(laneIds(0), 0, 500, 745, false, false, lanePropertiesValues1) + val createdSubLane2SplitA = NewLane(laneIds(1), 0, 100, 745, false, false, lanePropertiesValues2) + val createdSubLane2SplitB = NewLane(laneIds(2), 100, 200, 745, true, false, lanePropertiesValues2) + val createdSubLane2SplitC = NewLane(laneIds(3), 200, 300, 745, false, false, lanePropertiesValues2) + val createdSubLane2SplitD = NewLane(laneIds(4), 300, 400, 745, true, false, lanePropertiesValues2) + val createdSubLane2SplitE = NewLane(laneIds(5), 400, 500, 745, true, false, lanePropertiesValues2) + + ServiceWithDao.processNewLanes(Set(createdMainLane, createdSubLane2SplitA, createdSubLane2SplitB, createdSubLane2SplitC, createdSubLane2SplitD, createdSubLane2SplitE), Set(100L), 1, usernameTest, Seq()) + + val currentLanes = laneDao.fetchLanesByLinkIdsAndLaneCode(Seq(100L), Seq(1, 2), false) + currentLanes.size should be(3) + + val currentSplitLanes = currentLanes.filter(_.laneCode == 2).sortBy(_.startMeasure) + currentSplitLanes.size should be(2) + + currentSplitLanes(0).startMeasure should be(0) + currentSplitLanes(0).endMeasure should be(100) + currentSplitLanes(1).startMeasure should be(200) + currentSplitLanes(1).endMeasure should be(300) + + val expiredLanes = laneHistoryDao.fetchHistoryLanesByLinkIdsAndLaneCode(Seq(100L), Seq(2), true) + expiredLanes.size should be(3) + } + } } From 660d603cfe8aa586beaea6e0192e40b3dc82ee9f Mon Sep 17 00:00:00 2001 From: Juha-Pekka Tulijoki Date: Wed, 27 Apr 2022 09:09:10 +0300 Subject: [PATCH 3/7] DROTH-3109 improved lane split functionality after review --- UI/src/model/selectedLaneModelling.js | 15 ++++++---- .../view/linear_asset/laneModellingLayer.js | 16 +++++++++-- .../digiroad2/service/lane/LaneService.scala | 28 +++++++++---------- .../service/lane/LaneServiceSpec.scala | 28 +++++++++++++++++++ 4 files changed, 64 insertions(+), 23 deletions(-) diff --git a/UI/src/model/selectedLaneModelling.js b/UI/src/model/selectedLaneModelling.js index 2e5ace0c4f..ca5256e83b 100644 --- a/UI/src/model/selectedLaneModelling.js +++ b/UI/src/model/selectedLaneModelling.js @@ -78,13 +78,16 @@ return lane.endMeasure; }); - var charCount = 0; - for (var i = 0; i < lanesSortedByEndMeasure.length; i++) { - if (_.includes(laneCodesToPutMarkers, getLaneCodeValue(lanesSortedByEndMeasure[i]).toString())) { - lanesSortedByEndMeasure[i].marker = String.fromCharCode(charCount + 65); - charCount += 1; + _.forEach(laneCodesToPutMarkers, function (laneCode) { + var characterCounterForLaneMarker = 0; + for (var i = 0; i < lanesSortedByEndMeasure.length; i++) { + if (laneCode == getLaneCodeValue(lanesSortedByEndMeasure[i]).toString()) { + //The integer value of 'A' is 65, so every increment in the counter gives the next letter of the alphabet. + lanesSortedByEndMeasure[i].marker = String.fromCharCode(characterCounterForLaneMarker + 65); + characterCounterForLaneMarker += 1; + } } - } + }); return lanesSortedByEndMeasure; }; diff --git a/UI/src/view/linear_asset/laneModellingLayer.js b/UI/src/view/linear_asset/laneModellingLayer.js index 384a94a36d..21d763df18 100644 --- a/UI/src/view/linear_asset/laneModellingLayer.js +++ b/UI/src/view/linear_asset/laneModellingLayer.js @@ -81,8 +81,17 @@ return property.publicId === "lane_code"; }).values).value; - return !selectedLane.isOuterLane(laneNumber) || laneNumber == 1 || (!_.isUndefined(properties) && - properties.selectedLinks.length > 1 && _.isUndefined(properties.marker)) || selectedLane.isAddByRoadAddress(); + var uniqueSelectedRoadLinkIds = _.uniq(_.map(properties.selectedLinks, function (selectedLink) { + return selectedLink.linkId; + })); + if(_.isUndefined(uniqueSelectedRoadLinkIds)) { + return true; + } + + var SelectedLaneCannotBeCut = !selectedLane.isOuterLane(laneNumber) || laneNumber == 1 || laneNumber != selectedLane.getCurrentLaneNumber() || + uniqueSelectedRoadLinkIds.length > 1 || selectedLane.isAddByRoadAddress(); + + return SelectedLaneCannotBeCut; }) .map(function(feature) { var closestP = feature.getGeometry().getClosestPoint(point); @@ -119,7 +128,8 @@ return property.publicId === "lane_code"; }).values).value; - return !selectedLane.isOuterLane(laneNumber) || laneNumber == 1 || selectedLane.isAddByRoadAddress(); + return !selectedLane.isOuterLane(laneNumber) || laneNumber == 1 || + laneNumber != selectedLane.getCurrentLaneNumber() || selectedLane.isAddByRoadAddress(); }); selected = _.sortBy(selected, function (lane) { diff --git a/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/lane/LaneService.scala b/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/lane/LaneService.scala index 8bbfd835ae..4062b67705 100644 --- a/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/lane/LaneService.scala +++ b/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/lane/LaneService.scala @@ -579,7 +579,10 @@ trait LaneOperations { val oldLane = allExistingLanes.find(laneAux => laneAux.laneCode == lane.laneCode && laneAux.linkId == linkId) .getOrElse(throw new InvalidParameterException(s"LinkId: $linkId dont have laneCode: ${lane.laneCode} for update!")) - if (linkIds.size == 1 && lane.startMeasure == laneToUpdate.startMeasure && lane.startMeasure == laneToUpdate.startMeasure && lane.laneCode == laneToUpdateCode) { + val isExactlyMatchingSingleLinkLane = (lane: PersistedLane) => linkIds.size == 1 && + lane.startMeasure == laneToUpdate.startMeasure && lane.startMeasure == laneToUpdate.startMeasure && lane.laneCode == laneToUpdateCode + + if (isExactlyMatchingSingleLinkLane(lane)) { var newLaneID = lane.id if (isSomePropertyDifferent(lane, laneToUpdate.properties)) { val persistedLaneToUpdate = PersistedLane(lane.id, linkId, sideCode, laneToUpdateCode, lane.municipalityCode, @@ -925,35 +928,32 @@ trait LaneOperations { } def createMultiLanesOnLink(updateNewLanes: Seq[NewLane], linkIds: Set[Long], sideCode: Int, username: String): Seq[Long] = { - if (updateNewLanes.size == 0) { - return Seq() - } - // Get all lane codes from lanes to update - val laneCodesToModify = updateNewLanes.map { newLane => getLaneCode(newLane).toInt } - val startMeasureToModify = updateNewLanes.map(newLane => newLane.startMeasure).reduce(_ min _) - val endMeasureToModify = updateNewLanes.map(newLane => newLane.endMeasure).reduce(_ max _) - //Fetch from db the existing lanes that the new lanes will replace - val oldLanes = dao.fetchLanesByLinkIdsAndLaneCode(linkIds.toSeq, laneCodesToModify).filter(lane => lane.sideCode == sideCode - && lane.startMeasure >= startMeasureToModify && lane.endMeasure <= endMeasureToModify) + val laneCodesToModify = updateNewLanes.map { newLane => getLaneCode(newLane).toInt } + val oldLanes = dao.fetchLanesByLinkIdsAndLaneCode(linkIds.toSeq, laneCodesToModify).filter(lane => lane.sideCode == sideCode) val newLanesByLaneCode = updateNewLanes.groupBy(il => getLaneCode(il).toInt) //By lane check if exist something to modify newLanesByLaneCode.flatMap { case (laneCode, lanesToUpdate) => val oldLanesByCode = oldLanes.filter(_.laneCode == laneCode) - - if (lanesToUpdate.size > oldLanesByCode.size) { + if (lanesToUpdate.size >= 2) { //When one or more lanes are cut to smaller pieces val newLanesIDs = lanesToUpdate.map { lane => create(Seq(lane), linkIds, sideCode, username).head } + def isWithinRangeToExpire(newLanes: Seq[NewLane], oldLane: PersistedLane): Boolean = { + newLanes.filter(newLane => newLane.startMeasure == oldLane.startMeasure || newLane.endMeasure == oldLane.endMeasure).size == 2 + } + newLanesIDs.foreach { newLane => moveToHistory(oldLanesByCode.head.id, Some(newLane), true, false, username) } oldLanes.foreach {oldLane => - dao.deleteEntryLane(oldLane.id) + if (isWithinRangeToExpire(lanesToUpdate, oldLane)) { + dao.deleteEntryLane(oldLane.id) + } } newLanesIDs diff --git a/digiroad2-oracle/src/test/scala/fi/liikennevirasto/digiroad2/service/lane/LaneServiceSpec.scala b/digiroad2-oracle/src/test/scala/fi/liikennevirasto/digiroad2/service/lane/LaneServiceSpec.scala index f62c96d58a..f318543e50 100644 --- a/digiroad2-oracle/src/test/scala/fi/liikennevirasto/digiroad2/service/lane/LaneServiceSpec.scala +++ b/digiroad2-oracle/src/test/scala/fi/liikennevirasto/digiroad2/service/lane/LaneServiceSpec.scala @@ -1494,6 +1494,34 @@ class LaneServiceSpec extends LaneTestSupporter { } } + test("Split lane in both ends, leaving the middle untouched") { + runWithRollback { + val mainLane1 = NewLane(0, 0, 500, 745, false, false, lanePropertiesValues1) + val subLane2SplitA = NewLane(0, 0, 150, 745, false, false, lanePropertiesValues2) + val subLane2SplitB = NewLane(0, 150, 350, 745, false, false, lanePropertiesValues2) + val subLane2SplitC = NewLane(0, 350, 500, 745, false, false, lanePropertiesValues2) + val laneIds = ServiceWithDao.create(Seq(mainLane1, subLane2SplitA, subLane2SplitB, subLane2SplitC), Set(100L), 1, usernameTest) + + val initialLanes = laneDao.fetchLanesByLinkIdsAndLaneCode(Seq(100L), Seq(1, 2), false) + initialLanes.size should be(4) + + val existingMainLane1 = NewLane(laneIds(0), 0, 500, 745, false, false, lanePropertiesValues1) + val newSubLane2SplitA1 = NewLane(0, 0, 50, 745, false, false, lanePropertiesValues2) + val newSubLane2SplitA2 = NewLane(0, 50, 150, 745, false, false, lanePropertiesValues2) + val existingSubLane2SplitB = NewLane(laneIds(2), 150, 350, 745, false, false, lanePropertiesValues2) + val newSubLane2SplitC1 = NewLane(0, 350, 450, 745, false, false, lanePropertiesValues2) + val newSubLane2SplitC2 = NewLane(0, 450, 500, 745, false, false, lanePropertiesValues2) + + ServiceWithDao.processNewLanes(Set(existingMainLane1, newSubLane2SplitA1, newSubLane2SplitA2, existingSubLane2SplitB, newSubLane2SplitC1, newSubLane2SplitC2), Set(100L), 1, usernameTest, Seq()) + + val currentLanes = laneDao.fetchLanesByLinkIdsAndLaneCode(Seq(100L), Seq(1, 2), false) + currentLanes.size should be(6) + + val expiredLanes = laneHistoryDao.fetchHistoryLanesByLinkIdsAndLaneCode(Seq(100L), Seq(2), true) + expiredLanes.size should be(4) + } + } + test("Change properties of several splits") { runWithRollback { val mainLane1 = NewLane(0, 0, 500, 745, false, false, lanePropertiesValues1) From 9df78c7d75185795367af2a0d1b19f7f984b2bfd Mon Sep 17 00:00:00 2001 From: Juha-Pekka Tulijoki Date: Wed, 27 Apr 2022 11:29:01 +0300 Subject: [PATCH 4/7] DROTH-3109 improvements based on comments --- UI/src/model/selectedLaneModelling.js | 2 +- UI/src/view/linear_asset/laneModellingLayer.js | 1 + .../fi/liikennevirasto/digiroad2/service/lane/LaneService.scala | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/UI/src/model/selectedLaneModelling.js b/UI/src/model/selectedLaneModelling.js index ca5256e83b..1193f055a6 100644 --- a/UI/src/model/selectedLaneModelling.js +++ b/UI/src/model/selectedLaneModelling.js @@ -387,7 +387,7 @@ //expiredLane could be modified by the user so we need to fetch the original var originalExpiredLane = _.find(lanesFetched, {'id': expiredLane.id}); - if (linksSelected.length > 1 && marker === undefined) { + if (linksSelected.length > 1 && _.isUndefined(marker)) { var expiredGroup = collection.getGroup(originalExpiredLane); expiredGroup.forEach(function (lane) { lane.isExpired = true; diff --git a/UI/src/view/linear_asset/laneModellingLayer.js b/UI/src/view/linear_asset/laneModellingLayer.js index 21d763df18..b008aa9f10 100644 --- a/UI/src/view/linear_asset/laneModellingLayer.js +++ b/UI/src/view/linear_asset/laneModellingLayer.js @@ -17,6 +17,7 @@ var LinearAssetCutter = function(eventListener, vectorLayer) { var scissorFeatures = []; + //Max euclidean distance in geometry points allowed between mouse point and the closest linear asset point (the planned cut point). var CUT_THRESHOLD = 5; var vectorSource = vectorLayer.getSource(); diff --git a/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/lane/LaneService.scala b/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/lane/LaneService.scala index 4062b67705..b1d2f78620 100644 --- a/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/lane/LaneService.scala +++ b/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/lane/LaneService.scala @@ -895,7 +895,7 @@ trait LaneOperations { } //Get multiple lanes in one link - val resultWithMultiLanesInLink = newLanes.filter(lane => lane.isExpired != true && lane.id == 0).foldLeft(resultWithDeleteActions) { + val resultWithMultiLanesInLink = newLanes.filter(_.id == 0).foldLeft(resultWithDeleteActions) { (result, newLane) => val newLaneCode: Int = getPropertyValue(newLane.properties, "lane_code").toString.toInt From 2924d9a504390038aac44a427da13b6683c23d2b Mon Sep 17 00:00:00 2001 From: Juha-Pekka Tulijoki Date: Mon, 9 May 2022 10:47:10 +0300 Subject: [PATCH 5/7] DROTH-3109 Alter change messaging to acknowledge several lane splits --- .../liikennevirasto/digiroad2/lane/Lane.scala | 16 +- .../digiroad2/service/lane/LaneService.scala | 22 +- .../service/lane/LaneServiceSpec.scala | 225 +++++++++++++++++- 3 files changed, 244 insertions(+), 19 deletions(-) diff --git a/digiroad2-geo/src/main/scala/fi/liikennevirasto/digiroad2/lane/Lane.scala b/digiroad2-geo/src/main/scala/fi/liikennevirasto/digiroad2/lane/Lane.scala index ccdca027e9..7e23ad4eee 100644 --- a/digiroad2-geo/src/main/scala/fi/liikennevirasto/digiroad2/lane/Lane.scala +++ b/digiroad2-geo/src/main/scala/fi/liikennevirasto/digiroad2/lane/Lane.scala @@ -277,12 +277,12 @@ object LaneChangeType { values.find(_.value == value).getOrElse(Unknown) } - case object Add extends LaneChangeType { def value = 1; def description = "Lane is added normally";} - case object Lengthened extends LaneChangeType {def value = 2; def description = "Old lane is deleted and then a new lane is created with more length";} - case object Shortened extends LaneChangeType {def value = 3; def description = "Old lane is deleted and then a new lane is created with less length";} - case object Expired extends LaneChangeType {def value = 4; def description = "Lane is expired normally";} - case object LaneCodeTransfer extends LaneChangeType {def value = 5; def description = "Lane with some code was changed to another code";} - case object AttributesChanged extends LaneChangeType {def value = 6; def description = "Lane attributes were changed";} - case object Divided extends LaneChangeType {def value = 7; def description = "Old lane is deleted and then two more appear in same lane code";} - case object Unknown extends LaneChangeType {def value = 99; def description = "Unknown change to lane";} + case object Add extends LaneChangeType { def value = 1; def description = "A new lane is added from scratch";} + case object Lengthened extends LaneChangeType {def value = 2; def description = "The old lane is replaced with a longer new lane";} + case object Shortened extends LaneChangeType {def value = 3; def description = "The old lane is replaced with a shorter new lane";} + case object Expired extends LaneChangeType {def value = 4; def description = "A lane is expired with no replacements";} + case object LaneCodeTransfer extends LaneChangeType {def value = 5; def description = "The lane code is changed";} + case object AttributesChanged extends LaneChangeType {def value = 6; def description = "Some of the lane attributes are changed";} + case object Divided extends LaneChangeType {def value = 7; def description = "The old lane is replaced with two or more lane pieces";} + case object Unknown extends LaneChangeType {def value = 99; def description = "Unknown lane change";} } diff --git a/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/lane/LaneService.scala b/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/lane/LaneService.scala index b1d2f78620..cf60d059d7 100644 --- a/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/lane/LaneService.scala +++ b/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/lane/LaneService.scala @@ -371,7 +371,7 @@ trait LaneOperations { if (uptoDateLastModification.nonEmpty && upToDate.laneCode != uptoDateLastModification.minBy(_.historyCreatedDate.getMillis).laneCode) Some(LaneChange(upToDate, Some(historyLaneToPersistedLane(uptoDateLastModification.minBy(_.historyCreatedDate.getMillis))), LaneChangeType.LaneCodeTransfer, roadLink)) - else if (historyLanesWithRoadAddress.count(historyLane => historyLane.newId != 0 && historyLane.oldId == history.oldId) == 2) + else if (historyLanesWithRoadAddress.count(historyLane => historyLane.newId != 0 && historyLane.oldId == history.oldId) >= 2) Some(LaneChange(upToDate, Some(historyLaneToPersistedLane(history)), LaneChangeType.Divided, roadLink)) else if (upToDate.endMeasure - upToDate.startMeasure > history.endMeasure - history.startMeasure) @@ -422,7 +422,7 @@ trait LaneOperations { val newIdRelation = historyLanesWithRoadAddress.find(_.newId == laneAsPersistedLane.id) newIdRelation match { - case Some(relation) if historyLanesWithRoadAddress.count(historyLane => historyLane.newId != 0 && historyLane.oldId == relation.oldId) == 2 => + case Some(relation) if historyLanesWithRoadAddress.count(historyLane => historyLane.newId != 0 && historyLane.oldId == relation.oldId) >= 2 => Some(LaneChange(laneAsPersistedLane, Some(historyLaneToPersistedLane(relation)), LaneChangeType.Divided, roadLink)) case Some(relation) if laneAsPersistedLane.endMeasure - laneAsPersistedLane.startMeasure > relation.endMeasure - relation.startMeasure => @@ -943,15 +943,19 @@ trait LaneOperations { create(Seq(lane), linkIds, sideCode, username).head } - def isWithinRangeToExpire(newLanes: Seq[NewLane], oldLane: PersistedLane): Boolean = { - newLanes.filter(newLane => newLane.startMeasure == oldLane.startMeasure || newLane.endMeasure == oldLane.endMeasure).size == 2 - } + val createdNewLanes = dao.fetchLanesByIds(newLanesIDs.toSet) - newLanesIDs.foreach { newLane => - moveToHistory(oldLanesByCode.head.id, Some(newLane), true, false, username) + def newLanesWithinOldLaneRange(newLanes: Seq[PersistedLane], oldLane: PersistedLane) = { + newLanes.filter(newLane => newLane.startMeasure >= oldLane.startMeasure && newLane.endMeasure <= oldLane.endMeasure) } - oldLanes.foreach {oldLane => - if (isWithinRangeToExpire(lanesToUpdate, oldLane)) { + + //Expire only those old lanes that have been replaced by new lane pieces. + oldLanes.foreach { oldLane => + val newLanesWithinRange = newLanesWithinOldLaneRange(createdNewLanes, oldLane) + newLanesWithinRange.foreach { newLane => + moveToHistory(oldLane.id, Some(newLane.id), true, false, username) + } + if (!newLanesWithinRange.isEmpty) { dao.deleteEntryLane(oldLane.id) } } diff --git a/digiroad2-oracle/src/test/scala/fi/liikennevirasto/digiroad2/service/lane/LaneServiceSpec.scala b/digiroad2-oracle/src/test/scala/fi/liikennevirasto/digiroad2/service/lane/LaneServiceSpec.scala index f318543e50..d5905c1859 100644 --- a/digiroad2-oracle/src/test/scala/fi/liikennevirasto/digiroad2/service/lane/LaneServiceSpec.scala +++ b/digiroad2-oracle/src/test/scala/fi/liikennevirasto/digiroad2/service/lane/LaneServiceSpec.scala @@ -1442,6 +1442,23 @@ class LaneServiceSpec extends LaneTestSupporter { val expiredLanes = laneHistoryDao.fetchHistoryLanesByLinkIdsAndLaneCode(Seq(100L), Seq(2), true) expiredLanes.size should be(0) + + when(mockRoadLinkService.getRoadLinksByLinkIdsFromVVH(Set(100L), false)).thenReturn( + Seq(RoadLink(100L, Seq(Point(0.0, 0.0), Point(100.0, 0.0)), 100, Municipality, 1, TrafficDirection.BothDirections, Motorway, None, None, Map( + "MUNICIPALITYCODE" -> BigInt(745), + "ROADNUMBER" -> 100, + "ROADNAME_FI" -> "Testitie", + "VIITE_ROAD_PART_NUMBER" -> 7, + "VIITE_ROAD_NUMBER" -> 100, + "VIITE_END_ADDR" -> 2000 + ))) + ) + + val dateAtThisMoment = DateTime.now() + val laneChanges = ServiceWithDao.getChanged(dateAtThisMoment.minusDays(1), dateAtThisMoment.plusDays(1)) + + laneChanges.size should be(4) + laneChanges.count(_.changeType == LaneChangeType.Add) should be(4) } } @@ -1450,7 +1467,7 @@ class LaneServiceSpec extends LaneTestSupporter { val mainLane1 = NewLane(0, 0, 500, 745, false, false, lanePropertiesValues1) val subLane2 = NewLane(0, 0, 500, 745, false, false, lanePropertiesValues2) val mainLane1Id = ServiceWithDao.create(Seq(mainLane1), Set(100L), 1, usernameTest).head - ServiceWithDao.create(Seq(subLane2), Set(100L), 1, usernameTest) + val sublane2Id = ServiceWithDao.create(Seq(subLane2), Set(100L), 1, usernameTest) val initialLanes = laneDao.fetchLanesByLinkIdsAndLaneCode(Seq(100L), Seq(1, 2), false) initialLanes.size should be(2) @@ -1491,6 +1508,35 @@ class LaneServiceSpec extends LaneTestSupporter { val expiredLanes = laneHistoryDao.fetchHistoryLanesByLinkIdsAndLaneCode(Seq(100L), Seq(2), true) expiredLanes.size should be(3) + + when(mockRoadLinkService.getRoadLinksByLinkIdsFromVVH(Set(100L), false)).thenReturn( + Seq(RoadLink(100L, Seq(Point(0.0, 0.0), Point(100.0, 0.0)), 100, Municipality, 1, TrafficDirection.BothDirections, Motorway, None, None, Map( + "MUNICIPALITYCODE" -> BigInt(745), + "ROADNUMBER" -> 100, + "ROADNAME_FI" -> "Testitie", + "VIITE_ROAD_PART_NUMBER" -> 7, + "VIITE_ROAD_NUMBER" -> 100, + "VIITE_END_ADDR" -> 2000 + ))) + ) + + val dateAtThisMoment = DateTime.now() + val laneChanges = ServiceWithDao.getChanged(dateAtThisMoment.minusDays(1), dateAtThisMoment.plusDays(1)) + + // The change type for the main lane and the additional lane saved before split is add and the type for split lanes is divided. + laneChanges.size should be(5) + laneChanges.count(_.changeType == LaneChangeType.Add) should be(2) + laneChanges.count(_.changeType == LaneChangeType.Divided) should be(3) + + val divided = laneChanges.filter(_.changeType == LaneChangeType.Divided).sortBy(_.lane.startMeasure) + divided(0).lane.startMeasure should be(0) + divided(0).lane.endMeasure should be(150) + divided(1).lane.startMeasure should be(150) + divided(1).lane.endMeasure should be(350) + divided(2).lane.startMeasure should be(350) + divided(2).lane.endMeasure should be(500) + + divided.foreach(laneChange => laneChange.oldLane.get.id should be(sublane2Id.head)) } } @@ -1519,6 +1565,41 @@ class LaneServiceSpec extends LaneTestSupporter { val expiredLanes = laneHistoryDao.fetchHistoryLanesByLinkIdsAndLaneCode(Seq(100L), Seq(2), true) expiredLanes.size should be(4) + + when(mockRoadLinkService.getRoadLinksByLinkIdsFromVVH(Set(100L), false)).thenReturn( + Seq(RoadLink(100L, Seq(Point(0.0, 0.0), Point(100.0, 0.0)), 100, Municipality, 1, TrafficDirection.BothDirections, Motorway, None, None, Map( + "MUNICIPALITYCODE" -> BigInt(745), + "ROADNUMBER" -> 100, + "ROADNAME_FI" -> "Testitie", + "VIITE_ROAD_PART_NUMBER" -> 7, + "VIITE_ROAD_NUMBER" -> 100, + "VIITE_END_ADDR" -> 2000 + ))) + ) + + val dateAtThisMoment = DateTime.now() + val laneChanges = ServiceWithDao.getChanged(dateAtThisMoment.minusDays(1), dateAtThisMoment.plusDays(1)) + + /*The change type for the main lane and the additional split lanes saved before the second split is add. After two + of the split lanes are split further into four pieces total, the change type for these four split lanes is divided*/ + laneChanges.size should be(8) + laneChanges.count(_.changeType == LaneChangeType.Add) should be(4) + laneChanges.count(_.changeType == LaneChangeType.Divided) should be(4) + + //Check that the measures and old lane ids are correct in change message. + val divided = laneChanges.filter(_.changeType == LaneChangeType.Divided).sortBy(_.lane.startMeasure) + divided(0).lane.startMeasure should be(0) + divided(0).lane.endMeasure should be(50) + divided(0).oldLane.get.id should be(laneIds(1)) //the id for split lane A + divided(1).lane.startMeasure should be(50) + divided(1).lane.endMeasure should be(150) + divided(1).oldLane.get.id should be(laneIds(1)) + divided(2).lane.startMeasure should be(350) + divided(2).lane.endMeasure should be(450) + divided(2).oldLane.get.id should be(laneIds(3)) //the id for split lane C + divided(3).lane.startMeasure should be(450) + divided(3).lane.endMeasure should be(500) + divided(3).oldLane.get.id should be(laneIds(3)) } } @@ -1587,6 +1668,38 @@ class LaneServiceSpec extends LaneTestSupporter { val expiredLanes = laneHistoryDao.fetchHistoryLanesByLinkIdsAndLaneCode(Seq(100L), Seq(2), true) expiredLanes.size should be(3) + when(mockRoadLinkService.getRoadLinksByLinkIdsFromVVH(Set(100L), false)).thenReturn( + Seq(RoadLink(100L, Seq(Point(0.0, 0.0), Point(100.0, 0.0)), 100, Municipality, 1, TrafficDirection.BothDirections, Motorway, None, None, Map( + "MUNICIPALITYCODE" -> BigInt(745), + "ROADNUMBER" -> 100, + "ROADNAME_FI" -> "Testitie", + "VIITE_ROAD_PART_NUMBER" -> 7, + "VIITE_ROAD_NUMBER" -> 100, + "VIITE_END_ADDR" -> 2000 + ))) + ) + + val dateAtThisMoment = DateTime.now() + val lanesChanged = ServiceWithDao.getChanged(dateAtThisMoment.minusDays(1), dateAtThisMoment.plusDays(1)) + + lanesChanged.size should be(9) + + //There should a change type add for all created lanes. + lanesChanged.count(laneChange => laneChange.changeType == LaneChangeType.Add) should be(6) + //There should be a change type attributes changed for the three modified lanes. + lanesChanged.count(laneChange => laneChange.changeType == LaneChangeType.AttributesChanged) should be(3) + + //Check that the change information matches with the lanes changed. + val attributesChanged = lanesChanged.filter(_.changeType == LaneChangeType.AttributesChanged).sortBy(_.lane.startMeasure) + attributesChanged.map(_.lane.startMeasure) should be(Seq(100, 200, 400)) + attributesChanged.map(_.lane.endMeasure) should be(Seq(200, 300, 500)) + + attributesChanged.foreach{change => + change.oldLane.get.attributes.foreach(laneProp => lanePropertiesValues2.contains(laneProp) should be(true))} + + attributesChanged(0).lane.attributes.foreach(laneProp => newPropertyValues1.contains(laneProp) should be(true)) + attributesChanged(1).lane.attributes.foreach(laneProp => newPropertyValues2.contains(laneProp) should be(true)) + attributesChanged(2).lane.attributes.foreach(laneProp => newPropertyValues2.contains(laneProp) should be(true)) } } @@ -1625,6 +1738,114 @@ class LaneServiceSpec extends LaneTestSupporter { val expiredLanes = laneHistoryDao.fetchHistoryLanesByLinkIdsAndLaneCode(Seq(100L), Seq(2), true) expiredLanes.size should be(3) + + when(mockRoadLinkService.getRoadLinksByLinkIdsFromVVH(Set(100L), false)).thenReturn( + Seq(RoadLink(100L, Seq(Point(0.0, 0.0), Point(100.0, 0.0)), 100, Municipality, 1, TrafficDirection.BothDirections, Motorway, None, None, Map( + "MUNICIPALITYCODE" -> BigInt(745), + "ROADNUMBER" -> 100, + "ROADNAME_FI" -> "Testitie", + "VIITE_ROAD_PART_NUMBER" -> 7, + "VIITE_ROAD_NUMBER" -> 100, + "VIITE_END_ADDR" -> 2000 + ))) + ) + + val dateAtThisMoment = DateTime.now() + val lanesChanged = ServiceWithDao.getChanged(dateAtThisMoment.minusDays(1), dateAtThisMoment.plusDays(1)) + + lanesChanged.size should be(9) + + //There should a change type add for all created lanes. + lanesChanged.count(laneChange => laneChange.changeType == LaneChangeType.Add) should be(6) + //There should be a change type expired for the three expired lanes. + lanesChanged.count(laneChange => laneChange.changeType == LaneChangeType.Expired) should be(3) + + //Check that the measures of the expired lanes are correct in the change message. + val expired = lanesChanged.filter(_.changeType == LaneChangeType.Expired).sortBy(_.lane.startMeasure) + expired.map(_.lane.startMeasure) should be(Seq(100, 300, 400)) + expired.map(_.lane.endMeasure) should be(Seq(200, 400, 500)) } } -} + + test("Test deleted and shortened splits") { + runWithRollback { + val mainLane1 = NewLane(0, 0, 500, 745, false, false, lanePropertiesValues1) + val subLane2 = NewLane(0, 0, 500, 745, false, false, lanePropertiesValues2) + val mainLane1Id = ServiceWithDao.create(Seq(mainLane1), Set(100L), 1, usernameTest).head + val subLane2Id = ServiceWithDao.create(Seq(subLane2), Set(100L), 1, usernameTest) + + val initialLanes = laneDao.fetchLanesByLinkIdsAndLaneCode(Seq(100L), Seq(1, 2), false) + initialLanes.size should be(2) + + val createdMainLane = NewLane(mainLane1Id, 0, 500, 745, false, false, lanePropertiesValues1) + + val subLane2SplitA = NewLane(0, 0, 150, 745, false, false, lanePropertiesValues2) + val subLane2SplitB = NewLane(0, 150, 350, 745, false, false, lanePropertiesValues2) + + ServiceWithDao.processNewLanes(Set(createdMainLane, subLane2SplitA, subLane2SplitB), Set(100L), 1, usernameTest, Seq()) + + val subLane2SplitBs = NewLane(0, 150, 300, 745, false, false, lanePropertiesValues2) + + ServiceWithDao.processNewLanes(Set(createdMainLane, subLane2SplitA, subLane2SplitBs), Set(100L), 1, usernameTest, Seq()) + + val currentLanes = laneDao.fetchLanesByLinkIdsAndLaneCode(Seq(100L), Seq(1, 2), false) + currentLanes.size should be(3) + + val lane1 = currentLanes.filter(_.id == mainLane1Id).head + lane1.id should be(mainLane1Id) + lane1.attributes.foreach { laneProp => + lanePropertiesValues1.contains(laneProp) should be(true) + } + + val splitLanes = currentLanes.filter(_.laneCode == 2).sortBy(_.startMeasure) + splitLanes.size should be(2) + + splitLanes(0).startMeasure should be(0) + splitLanes(0).endMeasure should be(150) + splitLanes(1).startMeasure should be(150) + splitLanes(1).endMeasure should be(300) + + splitLanes.foreach { lane => + lane.attributes.foreach { + laneProp => + lanePropertiesValues2.contains(laneProp) should be(true) + } + } + when(mockRoadLinkService.getRoadLinksByLinkIdsFromVVH(Set(100L), false)).thenReturn( + Seq(RoadLink(100L, Seq(Point(0.0, 0.0), Point(100.0, 0.0)), 100, Municipality, 1, TrafficDirection.BothDirections, Motorway, None, None, Map( + "MUNICIPALITYCODE" -> BigInt(745), + "ROADNUMBER" -> 100, + "ROADNAME_FI" -> "Testitie", + "VIITE_ROAD_PART_NUMBER" -> 7, + "VIITE_ROAD_NUMBER" -> 100, + "VIITE_END_ADDR" -> 2000 + ))) + ) + + val dateAtThisMoment = DateTime.now() + val laneChanges = ServiceWithDao.getChanged(dateAtThisMoment.minusDays(1), dateAtThisMoment.plusDays(1)) + + /*The change type for the main lane and the original additional lane is add. When the additional lane is split into + two, the change type is divided even though the total length of the split lanes is less than the length of the original + lane. The change type for the shortened lane piece is shortened.*/ + laneChanges.size should be(5) + laneChanges.count(_.changeType == LaneChangeType.Add) should be(2) + laneChanges.count(_.changeType == LaneChangeType.Divided) should be(2) + laneChanges.count(_.changeType == LaneChangeType.Shortened) should be(1) + + //Check that measures and old lane ids are correct. + val divided = laneChanges.filter(_.changeType == LaneChangeType.Divided).sortBy(_.lane.startMeasure) + divided(0).lane.startMeasure should be(0) + divided(0).lane.endMeasure should be(150) + divided(0).oldLane.get.id should be(subLane2Id.head) + divided(1).lane.startMeasure should be(150) + divided(1).lane.endMeasure should be(350) + divided(1).oldLane.get.id should be(subLane2Id.head) + + val shortened = laneChanges.filter(_.changeType == LaneChangeType.Shortened).sortBy(_.lane.startMeasure) + shortened.head.lane.startMeasure should be(150) + shortened.head.lane.endMeasure should be(300) + shortened.head.oldLane.get.id should be(divided(1).lane.id) + } + } +} \ No newline at end of file From 77f0a04102b2a61eca4c6d7821cab9b6db1b0bdf Mon Sep 17 00:00:00 2001 From: Juha-Pekka Tulijoki Date: Fri, 3 Jun 2022 16:23:19 +0300 Subject: [PATCH 6/7] DROTH-3109 explicit expiration of deleted split lanes --- .../digiroad2/service/lane/LaneService.scala | 34 +++++- .../service/lane/LaneServiceSpec.scala | 112 ++++++++++++++++-- 2 files changed, 130 insertions(+), 16 deletions(-) diff --git a/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/lane/LaneService.scala b/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/lane/LaneService.scala index cf60d059d7..e59051c082 100644 --- a/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/lane/LaneService.scala +++ b/digiroad2-oracle/src/main/scala/fi/liikennevirasto/digiroad2/service/lane/LaneService.scala @@ -17,7 +17,6 @@ import org.joda.time.DateTime import org.slf4j.LoggerFactory import java.security.InvalidParameterException -import scala.collection.DebugUtils case class LaneChange(lane: PersistedLane, oldLane: Option[PersistedLane], changeType: LaneChangeType, roadLink: Option[RoadLink]) @@ -949,13 +948,40 @@ trait LaneOperations { newLanes.filter(newLane => newLane.startMeasure >= oldLane.startMeasure && newLane.endMeasure <= oldLane.endMeasure) } + /*Creates an explicit expired lane piece for those parts of the split line that have been deleted before saving. + Needed for change messages, so that the expiration change will be mapped to the expired lane part in ChangeAPI.*/ + def createExpiredPartsOfOldLane(newLanes: Seq[PersistedLane], oldLane: PersistedLane): Unit = { + val sortedNewLanes = newLanes.sortBy(_.startMeasure) + var start = oldLane.startMeasure + sortedNewLanes.foreach { newLane => + if (start < newLane.startMeasure) { + val replacementLaneId = create(Seq(NewLane(0, start, newLane.startMeasure, oldLane.municipalityCode, true, true, + oldLane.attributes, Some(sideCode))), linkIds, sideCode, username).head + moveToHistory(oldLane.id, Some(replacementLaneId), true, false, username) + moveToHistory(replacementLaneId, None, true, true, username) + start = newLane.startMeasure + } else { + start = newLane.endMeasure + } + + } + val lastNewLane = sortedNewLanes.last + if (lastNewLane.endMeasure < oldLane.endMeasure) { + val replacementLaneId = create(Seq(NewLane(0, lastNewLane.endMeasure, oldLane.endMeasure, oldLane.municipalityCode, true, true, + oldLane.attributes, Some(sideCode))), linkIds, sideCode, username).head + moveToHistory(oldLane.id, Some(replacementLaneId), true, false, username) + moveToHistory(replacementLaneId, None, true, true, username) + } + } + //Expire only those old lanes that have been replaced by new lane pieces. oldLanes.foreach { oldLane => val newLanesWithinRange = newLanesWithinOldLaneRange(createdNewLanes, oldLane) - newLanesWithinRange.foreach { newLane => - moveToHistory(oldLane.id, Some(newLane.id), true, false, username) - } if (!newLanesWithinRange.isEmpty) { + createExpiredPartsOfOldLane(newLanesWithinRange, oldLane) + newLanesWithinRange.foreach { newLane => + moveToHistory(oldLane.id, Some(newLane.id), true, false, username) + } dao.deleteEntryLane(oldLane.id) } } diff --git a/digiroad2-oracle/src/test/scala/fi/liikennevirasto/digiroad2/service/lane/LaneServiceSpec.scala b/digiroad2-oracle/src/test/scala/fi/liikennevirasto/digiroad2/service/lane/LaneServiceSpec.scala index d5905c1859..b947d89025 100644 --- a/digiroad2-oracle/src/test/scala/fi/liikennevirasto/digiroad2/service/lane/LaneServiceSpec.scala +++ b/digiroad2-oracle/src/test/scala/fi/liikennevirasto/digiroad2/service/lane/LaneServiceSpec.scala @@ -1824,28 +1824,116 @@ class LaneServiceSpec extends LaneTestSupporter { val dateAtThisMoment = DateTime.now() val laneChanges = ServiceWithDao.getChanged(dateAtThisMoment.minusDays(1), dateAtThisMoment.plusDays(1)) + /*The change type for the main lane and the original additional lane is add. When the additional lane is split, there + will be three divided messages, two for remaining pieces and one for the removed part, and an explicit + expire message for the removed part. Moreover, there will be two divided and one expire for the second split*/ + laneChanges.size should be(9) - /*The change type for the main lane and the original additional lane is add. When the additional lane is split into - two, the change type is divided even though the total length of the split lanes is less than the length of the original - lane. The change type for the shortened lane piece is shortened.*/ - laneChanges.size should be(5) laneChanges.count(_.changeType == LaneChangeType.Add) should be(2) - laneChanges.count(_.changeType == LaneChangeType.Divided) should be(2) - laneChanges.count(_.changeType == LaneChangeType.Shortened) should be(1) + laneChanges.count(_.changeType == LaneChangeType.Divided) should be(5) + laneChanges.count(_.changeType == LaneChangeType.Expired) should be(2) //Check that measures and old lane ids are correct. + val divided = laneChanges.filter(_.changeType == LaneChangeType.Divided).sortBy(c => (c.lane.startMeasure, c.lane.endMeasure)) + divided(0).lane.startMeasure should be(0) + divided(0).lane.endMeasure should be(150) + divided(1).lane.startMeasure should be(150) + divided(1).lane.endMeasure should be(300) + divided(2).lane.startMeasure should be(150) + divided(2).lane.endMeasure should be(350) + divided(3).lane.startMeasure should be(300) + divided(3).lane.endMeasure should be(350) + divided(4).lane.startMeasure should be(350) + divided(4).lane.endMeasure should be(500) + + val expired = laneChanges.filter(_.changeType == LaneChangeType.Expired).sortBy(_.lane.startMeasure) + expired(0).lane.startMeasure should be(300) + expired(0).lane.endMeasure should be(350) + expired(1).lane.startMeasure should be(350) + expired(1).lane.endMeasure should be(500) + } + } + + test("Split existing lane in three pieces and remove the middle part") { + runWithRollback { + val mainLane1 = NewLane(0, 0, 500, 745, false, false, lanePropertiesValues1) + val subLane2 = NewLane(0, 0, 500, 745, false, false, lanePropertiesValues2) + val mainLane1Id = ServiceWithDao.create(Seq(mainLane1), Set(100L), 1, usernameTest).head + val sublane2Id = ServiceWithDao.create(Seq(subLane2), Set(100L), 1, usernameTest) + + val initialLanes = laneDao.fetchLanesByLinkIdsAndLaneCode(Seq(100L), Seq(1, 2), false) + initialLanes.size should be(2) + + val createdMainLane = NewLane(mainLane1Id, 0, 500, 745, false, false, lanePropertiesValues1) + + val subLane2SplitA = NewLane(0, 0, 150, 745, false, false, lanePropertiesValues2) + val subLane2SplitB = NewLane(0, 350, 500, 745, false, false, lanePropertiesValues2) + + ServiceWithDao.processNewLanes(Set(createdMainLane, subLane2SplitA, subLane2SplitB), Set(100L), 1, usernameTest, Seq()) + + val currentLanes = laneDao.fetchLanesByLinkIdsAndLaneCode(Seq(100L), Seq(1, 2), false) + currentLanes.size should be(3) + + val lane1 = currentLanes.filter(_.id == mainLane1Id).head + lane1.id should be(mainLane1Id) + lane1.attributes.foreach { laneProp => + lanePropertiesValues1.contains(laneProp) should be(true) + } + + val splitLanes = currentLanes.filter(_.laneCode == 2).sortBy(_.startMeasure) + splitLanes.size should be(2) + + splitLanes(0).startMeasure should be(0) + splitLanes(0).endMeasure should be(150) + splitLanes(1).startMeasure should be(350) + splitLanes(1).endMeasure should be(500) + + splitLanes.foreach { lane => + lane.attributes.foreach { + laneProp => + lanePropertiesValues2.contains(laneProp) should be(true) + } + } + + // Three expired parts for the division of the big lane and one that is linked to the expired piece. + val expiredLanes = laneHistoryDao.fetchHistoryLanesByLinkIdsAndLaneCode(Seq(100L), Seq(2), true) + expiredLanes.size should be(4) + + when(mockRoadLinkService.getRoadLinksByLinkIdsFromVVH(Set(100L), false)).thenReturn( + Seq(RoadLink(100L, Seq(Point(0.0, 0.0), Point(100.0, 0.0)), 100, Municipality, 1, TrafficDirection.BothDirections, Motorway, None, None, Map( + "MUNICIPALITYCODE" -> BigInt(745), + "ROADNUMBER" -> 100, + "ROADNAME_FI" -> "Testitie", + "VIITE_ROAD_PART_NUMBER" -> 7, + "VIITE_ROAD_NUMBER" -> 100, + "VIITE_END_ADDR" -> 2000 + ))) + ) + + val dateAtThisMoment = DateTime.now() + val laneChanges = ServiceWithDao.getChanged(dateAtThisMoment.minusDays(1), dateAtThisMoment.plusDays(1)) + + /*The change type for the main lane and the additional lane saved before split is add and the type for split lanes is divided. + In addition, there is an explicit expire message for the removed part*/ + laneChanges.size should be(6) + laneChanges.count(_.changeType == LaneChangeType.Add) should be(2) + laneChanges.count(_.changeType == LaneChangeType.Divided) should be(3) + laneChanges.count(_.changeType == LaneChangeType.Expired) should be(1) + val divided = laneChanges.filter(_.changeType == LaneChangeType.Divided).sortBy(_.lane.startMeasure) divided(0).lane.startMeasure should be(0) divided(0).lane.endMeasure should be(150) - divided(0).oldLane.get.id should be(subLane2Id.head) divided(1).lane.startMeasure should be(150) divided(1).lane.endMeasure should be(350) - divided(1).oldLane.get.id should be(subLane2Id.head) + divided(2).lane.startMeasure should be(350) + divided(2).lane.endMeasure should be(500) + + divided.foreach(laneChange => laneChange.oldLane.get.id should be(sublane2Id.head)) + + val expired = laneChanges.filter(_.changeType == LaneChangeType.Expired).head - val shortened = laneChanges.filter(_.changeType == LaneChangeType.Shortened).sortBy(_.lane.startMeasure) - shortened.head.lane.startMeasure should be(150) - shortened.head.lane.endMeasure should be(300) - shortened.head.oldLane.get.id should be(divided(1).lane.id) + expired.lane.startMeasure should be(150) + expired.lane.endMeasure should be(350) } } } \ No newline at end of file From e4e1551a0caf4e39982376e31f15fa005d5407e5 Mon Sep 17 00:00:00 2001 From: Juha-Pekka Tulijoki Date: Mon, 6 Jun 2022 13:43:52 +0300 Subject: [PATCH 7/7] DROTH-3109 fix one unit-test by adding sidecode --- .../digiroad2/service/lane/LaneServiceSpec.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/digiroad2-oracle/src/test/scala/fi/liikennevirasto/digiroad2/service/lane/LaneServiceSpec.scala b/digiroad2-oracle/src/test/scala/fi/liikennevirasto/digiroad2/service/lane/LaneServiceSpec.scala index b947d89025..a3ec27fc8f 100644 --- a/digiroad2-oracle/src/test/scala/fi/liikennevirasto/digiroad2/service/lane/LaneServiceSpec.scala +++ b/digiroad2-oracle/src/test/scala/fi/liikennevirasto/digiroad2/service/lane/LaneServiceSpec.scala @@ -7,7 +7,7 @@ import fi.liikennevirasto.digiroad2.client.vvh.{VVHClient, VVHRoadLinkClient} import fi.liikennevirasto.digiroad2.dao.{MunicipalityDao, RoadAddressTEMP} import fi.liikennevirasto.digiroad2.dao.lane.{LaneDao, LaneHistoryDao} import fi.liikennevirasto.digiroad2.lane.LaneFiller.{ChangeSet, SideCodeAdjustment} -import fi.liikennevirasto.digiroad2.lane.{LaneChangeType, LaneFiller, LaneNumberOneDigit, LaneProperty, LanePropertyValue, NewLane, PersistedLane, PieceWiseLane} +import fi.liikennevirasto.digiroad2.lane.{LaneChangeType, LaneFiller, LaneNumberOneDigit, LaneProperty, LanePropertyValue, NewLane, PersistedLane, PieceWiseLane, SideCodesForLinkIds} import fi.liikennevirasto.digiroad2.linearasset.RoadLink import fi.liikennevirasto.digiroad2.postgis.PostGISDatabase import fi.liikennevirasto.digiroad2.service.{RoadAddressService, RoadLinkService} @@ -1861,6 +1861,9 @@ class LaneServiceSpec extends LaneTestSupporter { val mainLane1Id = ServiceWithDao.create(Seq(mainLane1), Set(100L), 1, usernameTest).head val sublane2Id = ServiceWithDao.create(Seq(subLane2), Set(100L), 1, usernameTest) + val sideCodeForLink100 = SideCodesForLinkIds(100L, 1) + val sideCodesForLinkIds = Seq(sideCodeForLink100) + val initialLanes = laneDao.fetchLanesByLinkIdsAndLaneCode(Seq(100L), Seq(1, 2), false) initialLanes.size should be(2) @@ -1869,7 +1872,7 @@ class LaneServiceSpec extends LaneTestSupporter { val subLane2SplitA = NewLane(0, 0, 150, 745, false, false, lanePropertiesValues2) val subLane2SplitB = NewLane(0, 350, 500, 745, false, false, lanePropertiesValues2) - ServiceWithDao.processNewLanes(Set(createdMainLane, subLane2SplitA, subLane2SplitB), Set(100L), 1, usernameTest, Seq()) + ServiceWithDao.processNewLanes(Set(createdMainLane, subLane2SplitA, subLane2SplitB), Set(100L), 1, usernameTest, sideCodesForLinkIds) val currentLanes = laneDao.fetchLanesByLinkIdsAndLaneCode(Seq(100L), Seq(1, 2), false) currentLanes.size should be(3)