From 47af8f5d5ba095534fc61cde7053835f5ea9f4ef Mon Sep 17 00:00:00 2001 From: Ricardo Ewert Date: Tue, 26 Mar 2024 13:08:54 +0100 Subject: [PATCH] split landuseAnalysis in a separate class und add commercial facilities as output for this analysis --- ...rateSmallScaleCommercialTrafficDemand.java | 138 ++++++------ .../SmallScaleCommercialTrafficUtils.java | 95 ++++----- ...CreateDataDistributionOfStructureData.java | 151 +++++++++++++ .../LanduseBuildingAnalysis.java | 199 +++++++++++------- .../SCTUtils.java | 8 +- .../SmallScaleCommercialTrafficUtilsTest.java | 11 +- .../TrafficVolumeGenerationTest.java | 59 +++--- .../TripDistributionMatrixTest.java | 12 +- ...teDataDistributionOfStructureDataTest.java | 50 +++++ .../LanduseBuildingAnalysisTest.java | 46 ++-- .../commercialFacilities.xml.gz | Bin 0 -> 1378 bytes .../test.output_events.xml.gz | Bin 31240 -> 31397 bytes 12 files changed, 495 insertions(+), 274 deletions(-) create mode 100644 contribs/small-scale-traffic-generation/src/main/java/org/matsim/smallScaleCommercialTrafficGeneration/prepare/CreateDataDistributionOfStructureData.java rename contribs/small-scale-traffic-generation/src/main/java/org/matsim/smallScaleCommercialTrafficGeneration/{ => prepare}/LanduseBuildingAnalysis.java (70%) create mode 100644 contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/prepare/CreateDataDistributionOfStructureDataTest.java rename contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/{ => prepare}/LanduseBuildingAnalysisTest.java (92%) create mode 100644 contribs/small-scale-traffic-generation/test/input/org/matsim/smallScaleCommercialTrafficGeneration/commercialFacilities.xml.gz diff --git a/contribs/small-scale-traffic-generation/src/main/java/org/matsim/smallScaleCommercialTrafficGeneration/GenerateSmallScaleCommercialTrafficDemand.java b/contribs/small-scale-traffic-generation/src/main/java/org/matsim/smallScaleCommercialTrafficGeneration/GenerateSmallScaleCommercialTrafficDemand.java index e5b6a27b774..57328df62cc 100644 --- a/contribs/small-scale-traffic-generation/src/main/java/org/matsim/smallScaleCommercialTrafficGeneration/GenerateSmallScaleCommercialTrafficDemand.java +++ b/contribs/small-scale-traffic-generation/src/main/java/org/matsim/smallScaleCommercialTrafficGeneration/GenerateSmallScaleCommercialTrafficDemand.java @@ -26,7 +26,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.config.Configurator; -import org.locationtech.jts.geom.Geometry; import org.matsim.api.core.v01.Coord; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.Scenario; @@ -62,7 +61,8 @@ import org.matsim.core.scoring.SumScoringFunction; import org.matsim.core.utils.geometry.CoordUtils; import org.matsim.core.utils.geometry.CoordinateTransformation; -import org.matsim.core.utils.geometry.geotools.MGC; +import org.matsim.facilities.ActivityFacilities; +import org.matsim.facilities.ActivityFacility; import org.matsim.freight.carriers.*; import org.matsim.freight.carriers.CarrierCapabilities.FleetSize; import org.matsim.freight.carriers.controler.*; @@ -82,6 +82,7 @@ import java.util.stream.Collectors; import static org.matsim.core.controler.OutputDirectoryHierarchy.OverwriteFileSetting.overwriteExistingFiles; +import static org.matsim.smallScaleCommercialTrafficGeneration.SmallScaleCommercialTrafficUtils.readDataDistribution; /** * Tool to generate small scale commercial traffic for a selected area. The needed input data are: employee information for the area and three shapes files (zones, buildings, landuse). These data should be available with OSM. @@ -106,10 +107,6 @@ private enum CreationOption { useExistingCarrierFileWithSolution, createNewCarrierFile, useExistingCarrierFileWithoutSolution } - private enum LanduseConfiguration { - useOnlyOSMLanduse, useOSMBuildingsAndLanduse, useExistingDataDistribution - } - private enum SmallScaleCommercialTrafficType { commercialPersonTraffic, goodsTraffic, completeSmallScaleCommercialTraffic } @@ -117,11 +114,11 @@ private enum SmallScaleCommercialTrafficType { @CommandLine.Parameters(arity = "1", paramLabel = "INPUT", description = "Path to the config for small scale commercial generation") private Path configPath; - @CommandLine.Option(names = "--pathToInvestigationAreaData", description = "Path to the investigation area data") - private Path pathToInvestigationAreaData; + @CommandLine.Option(names = "--pathToDataDistributionToZones", description = "Path to the data distribution to zones") + private Path pathToDataDistributionToZones; - @CommandLine.Option(names = "--pathToExistingDataDistributionToZones", description = "Path to the existing data distribution to zones. This is only needed if the option useExistingDataDistribution is selected.") - private Path pathToExistingDataDistributionToZones; + @CommandLine.Option(names = "--pathToCommercialFacilities", description = "Path to the commercial facilities.") + private Path pathToCommercialFacilities; @CommandLine.Option(names = "--sample", description = "Scaling factor of the small scale commercial traffic (0, 1)", required = true) private double sample; @@ -184,13 +181,9 @@ private enum SmallScaleCommercialTrafficType { private Path output; private Random rnd; - private final Map>> buildingsPerZone = new HashMap<>(); - private final Map> landuseCategoriesAndDataConnection = new HashMap<>(); + private final Map>> facilitiesPerZone = new HashMap<>(); private Index indexZones; - private Index indexBuildings; - private Index indexLanduse; - private Index indexInvestigationAreaRegions; public static void main(String[] args) { System.exit(new CommandLine(new GenerateSmallScaleCommercialTrafficDemand()).execute(args)); @@ -237,31 +230,14 @@ public Integer call() throws Exception { solveSeparatedVRPs(scenario, null); } default -> { - if (!Files.exists(shapeFileLandusePath)) { - throw new Exception("Required landuse shape file not found:" + shapeFileLandusePath.toString()); - } - if (!Files.exists(shapeFileBuildingsPath)) { - throw new Exception( - "Required OSM buildings shape file {} not found" + shapeFileBuildingsPath.toString()); - } if (!Files.exists(shapeFileZonePath)) { throw new Exception("Required districts shape file {} not found" + shapeFileZonePath.toString()); } - if (!Files.exists(shapeFileRegionsPath)) { - throw new Exception("Required regions shape file {} not found" + shapeFileRegionsPath.toString()); - } - indexZones = SmallScaleCommercialTrafficUtils.getIndexZones(shapeFileZonePath, shapeCRS, shapeFileZoneNameColumn); - indexBuildings = SmallScaleCommercialTrafficUtils.getIndexBuildings(shapeFileBuildingsPath, shapeCRS, shapeFileBuildingTypeColumn); - indexLanduse = SmallScaleCommercialTrafficUtils.getIndexLanduse(shapeFileLandusePath, shapeCRS, shapeFileLanduseTypeColumn); - indexInvestigationAreaRegions = SmallScaleCommercialTrafficUtils.getIndexRegions(shapeFileRegionsPath, shapeCRS, regionsShapeRegionColumn); - Map> resultingDataPerZone = LanduseBuildingAnalysis - .createInputDataDistribution(output, landuseCategoriesAndDataConnection, - usedLanduseConfiguration.toString(), indexLanduse, indexZones, - indexBuildings, indexInvestigationAreaRegions, shapeFileZoneNameColumn, buildingsPerZone, pathToInvestigationAreaData, - pathToExistingDataDistributionToZones); - Map, Link>> linksPerZone = filterLinksForZones(scenario, indexZones, buildingsPerZone); + Map> resultingDataPerZone = readDataDistribution(pathToDataDistributionToZones); + filterFacilitiesForZones(scenario, facilitiesPerZone); + Map, Link>> linksPerZone = filterLinksForZones(scenario, indexZones, facilitiesPerZone); switch (usedSmallScaleCommercialTrafficType) { case commercialPersonTraffic, goodsTraffic -> @@ -311,11 +287,24 @@ public Integer call() throws Exception { return 0; } + /** Creates + * @param scenario + * @param facilitiesPerZone + */ + private void filterFacilitiesForZones(Scenario scenario, Map>> facilitiesPerZone) { + scenario.getActivityFacilities().getFacilities().values().forEach((activityFacility -> { + activityFacility.getActivityOptions().values().forEach(activityOption -> { + facilitiesPerZone.computeIfAbsent((String) activityFacility.getAttributes().getAttribute("zone"), k -> new HashMap<>()) + .computeIfAbsent(activityOption.getType(), k -> new ArrayList<>()).add(activityFacility); + }); + })); + } + /** * @param originalScenario complete Scenario - * @param regionLinksMap list with Links for each region + * @param linksPerZone list with Links for each region */ - private void solveSeparatedVRPs(Scenario originalScenario, Map, Link>> regionLinksMap) throws Exception { + private void solveSeparatedVRPs(Scenario originalScenario, Map, Link>> linksPerZone) throws Exception { boolean splitCarrier = true; boolean splitVRPs = false; @@ -432,11 +421,11 @@ private void solveSeparatedVRPs(Scenario originalScenario, Map { - if (regionLinksMap != null && !carrier.getAttributes().getAsMap().containsKey("tourStartArea")) { + if (linksPerZone != null && !carrier.getAttributes().getAsMap().containsKey("tourStartArea")) { List startAreas = new ArrayList<>(); for (ScheduledTour tour : carrier.getSelectedPlan().getScheduledTours()) { String tourStartZone = SmallScaleCommercialTrafficUtils - .findZoneOfLink(tour.getTour().getStartLinkId(), regionLinksMap); + .findZoneOfLink(tour.getTour().getStartLinkId(), linksPerZone); if (!startAreas.contains(tourStartZone)) startAreas.add(tourStartZone); } @@ -498,7 +487,7 @@ private Config readAndCheckConfig(Path configPath, String modelName, String samp config.transit().setTransitScheduleFile(null); config.transit().setVehiclesFile(null); config.counts().setInputFile(null); - + config.facilities().setInputFile(pathToCommercialFacilities.toString()); // Set flow and storage capacity to a high value config.qsim().setFlowCapFactor(sample * 4); config.qsim().setStorageCapFactor(sample * 4); @@ -553,7 +542,7 @@ public void install() { */ private void createCarriers(Scenario scenario, TripDistributionMatrix odMatrix, Map> resultingDataPerZone, String smallScaleCommercialTrafficType, - Map, Link>> regionLinksMap) { + Map, Link>> linksPerZone) { int maxNumberOfCarrier = odMatrix.getListOfPurposes().size() * odMatrix.getListOfZones().size() * odMatrix.getListOfModesOrVehTypes().size(); int createdCarrier = 0; @@ -689,7 +678,7 @@ private void createCarriers(Scenario scenario, TripDistributionMatrix odMatrix, purpose, smallScaleCommercialTrafficType) / occupancyRate)); createNewCarrierAndAddVehicleTypes(scenario, purpose, startZone, selectedStartCategory, carrierName, vehicleTypes, numberOfDepots, fleetSize, - fixedNumberOfVehiclePerTypeAndLocation, vehicleDepots, regionLinksMap, smallScaleCommercialTrafficType, + fixedNumberOfVehiclePerTypeAndLocation, vehicleDepots, linksPerZone, smallScaleCommercialTrafficType, tourStartTimeSelector, tourDurationTimeSelector); log.info("Create services for carrier: " + carrierName); for (String stopZone : odMatrix.getListOfZones()) { @@ -712,7 +701,7 @@ private void createCarriers(Scenario scenario, TripDistributionMatrix odMatrix, TimeWindow serviceTimeWindow = TimeWindow.newInstance(0, 24 * 3600); //TODO eventuell anpassen wegen veränderter Tourzeiten createServices(scenario, vehicleDepots, selectedStopCategory, carrierName, - numberOfJobs, serviceArea, serviceTimePerStop, serviceTimeWindow, regionLinksMap); + numberOfJobs, serviceArea, serviceTimePerStop, serviceTimeWindow, linksPerZone); } } } @@ -739,13 +728,13 @@ private void createCarriers(Scenario scenario, TripDistributionMatrix odMatrix, private void createServices(Scenario scenario, ArrayList noPossibleLinks, String selectedStopCategory, String carrierName, int numberOfJobs, String[] serviceArea, Integer serviceTimePerStop, TimeWindow serviceTimeWindow, - Map, Link>> regionLinksMap) { + Map, Link>> linksPerZone) { String stopZone = serviceArea[0]; for (int i = 0; i < numberOfJobs; i++) { - Id linkId = findPossibleLink(stopZone, selectedStopCategory, noPossibleLinks, regionLinksMap); + Id linkId = findPossibleLink(stopZone, selectedStopCategory, noPossibleLinks, linksPerZone, scenario.getActivityFacilities()); Id idNewService = Id.create(carrierName + "_" + linkId + "_" + rnd.nextInt(10000), CarrierService.class); @@ -764,7 +753,7 @@ private void createNewCarrierAndAddVehicleTypes(Scenario scenario, Integer purpo String selectedStartCategory, String carrierName, List vehicleTypes, int numberOfDepots, FleetSize fleetSize, int fixedNumberOfVehiclePerTypeAndLocation, - List vehicleDepots, Map, Link>> regionLinksMap, + List vehicleDepots, Map, Link>> linksPerZone, String smallScaleCommercialTrafficType, ValueSelectorUnderGivenProbability tourStartTimeSelector, ValueSelectorUnderGivenProbability tourDurationTimeSelector) { @@ -788,7 +777,7 @@ private void createNewCarrierAndAddVehicleTypes(Scenario scenario, Integer purpo carriers.addCarrier(thisCarrier); while (vehicleDepots.size() < numberOfDepots) { - Id link = findPossibleLink(startZone, selectedStartCategory, null, regionLinksMap); + Id link = findPossibleLink(startZone, selectedStartCategory, null, linksPerZone, scenario.getActivityFacilities()); vehicleDepots.add(link.toString()); } @@ -877,31 +866,25 @@ else if (smallScaleCommercialTrafficType.equals(SmallScaleCommercialTrafficType. * Finds a possible link for a service or the vehicle location. */ private Id findPossibleLink(String zone, String selectedCategory, List noPossibleLinks, - Map, Link>> regionLinksMap) { + Map, Link>> linksPerZone, ActivityFacilities activityFacilities) { - if (buildingsPerZone.isEmpty()) { - List buildingsFeatures = indexBuildings.getAllFeatures(); - LanduseBuildingAnalysis.analyzeBuildingType(buildingsFeatures, buildingsPerZone, - landuseCategoriesAndDataConnection, indexLanduse, indexZones); - } Id newLink = null; - for (int a = 0; newLink == null && a < buildingsPerZone.get(zone).get(selectedCategory).size() * 2; a++) { + for (int a = 0; newLink == null && a < facilitiesPerZone.get(zone).get(selectedCategory).size() * 2; a++) { - SimpleFeature possibleBuilding = buildingsPerZone.get(zone).get(selectedCategory) - .get(rnd.nextInt(buildingsPerZone.get(zone).get(selectedCategory).size())); - Coord centroidPointOfBuildingPolygon = MGC - .point2Coord(((Geometry) possibleBuilding.getDefaultGeometry()).getCentroid()); + ActivityFacility possibleBuilding = facilitiesPerZone.get(zone).get(selectedCategory) + .get(rnd.nextInt(facilitiesPerZone.get(zone).get(selectedCategory).size())); //TODO Wkt für die Auswahl anpassen + Coord centroidPointOfBuildingPolygon = possibleBuilding.getCoord(); - int numberOfPossibleLinks = regionLinksMap.get(zone).size(); + int numberOfPossibleLinks = linksPerZone.get(zone).size(); // searches and selects the nearest link of the possible links in this zone - newLink = SmallScaleCommercialTrafficUtils.findNearestPossibleLink(zone, noPossibleLinks, regionLinksMap, newLink, + newLink = SmallScaleCommercialTrafficUtils.findNearestPossibleLink(zone, noPossibleLinks, linksPerZone, newLink, centroidPointOfBuildingPolygon, numberOfPossibleLinks); } if (newLink == null) throw new RuntimeException("No possible link for buildings with type '" + selectedCategory + "' in zone '" - + zone + "' found. buildings in category: " + buildingsPerZone.get(zone).get(selectedCategory) - + "; possibleLinks in zone: " + regionLinksMap.get(zone).size()); + + zone + "' found. buildings in category: " + facilitiesPerZone.get(zone).get(selectedCategory) + + "; possibleLinks in zone: " + linksPerZone.get(zone).size()); return newLink; } @@ -909,8 +892,8 @@ private Id findPossibleLink(String zone, String selectedCategory, List, Link>> filterLinksForZones(Scenario scenario, Index indexZones, - Map>> buildingsPerZone) throws URISyntaxException { - Map, Link>> regionLinksMap = new HashMap<>(); + Map>> facilitiesPerZone) throws URISyntaxException { + Map, Link>> linksPerZone = new HashMap<>(); List links; log.info("Filtering and assign links to zones. This take some time..."); @@ -932,30 +915,29 @@ static Map, Link>> filterLinksForZones(Scenario scenario, I links.forEach(l -> l.getAttributes().putAttribute("zone", indexZones.query((Coord) l.getAttributes().getAttribute("newCoord")))); links = links.stream().filter(l -> l.getAttributes().getAttribute("zone") != null).collect(Collectors.toList()); - links.forEach(l -> regionLinksMap + links.forEach(l -> linksPerZone .computeIfAbsent((String) l.getAttributes().getAttribute("zone"), (k) -> new HashMap<>()) .put(l.getId(), l)); - if (regionLinksMap.size() != indexZones.size()) - findNearestLinkForZonesWithoutLinks(networkToChange, regionLinksMap, indexZones, buildingsPerZone); + if (linksPerZone.size() != indexZones.size()) + findNearestLinkForZonesWithoutLinks(networkToChange, linksPerZone, indexZones, facilitiesPerZone); - return regionLinksMap; + return linksPerZone; } /** * Finds for areas without links the nearest Link if the area contains any building. */ - private static void findNearestLinkForZonesWithoutLinks(Network networkToChange, Map, Link>> regionLinksMap, + private static void findNearestLinkForZonesWithoutLinks(Network networkToChange, Map, Link>> linksPerZone, Index shpZones, - Map>> buildingsPerZone) { + Map>> facilitiesPerZone) { for (SimpleFeature singleArea : shpZones.getAllFeatures()) { String zoneID = (String) singleArea.getAttribute("areaID"); - if (!regionLinksMap.containsKey(zoneID) && buildingsPerZone.get(zoneID) != null) { - for (List buildingList : buildingsPerZone.get(zoneID).values()) { - for (SimpleFeature building : buildingList) { - Link l = NetworkUtils.getNearestLink(networkToChange, - MGC.point2Coord(((Geometry) building.getDefaultGeometry()).getCentroid())); + if (!linksPerZone.containsKey(zoneID) && facilitiesPerZone.get(zoneID) != null) { + for (List buildingList : facilitiesPerZone.get(zoneID).values()) { + for (ActivityFacility building : buildingList) { + Link l = NetworkUtils.getNearestLink(networkToChange, building.getCoord()); assert l != null; - regionLinksMap + linksPerZone .computeIfAbsent(zoneID, (k) -> new HashMap<>()) .put(l.getId(), l); } @@ -970,7 +952,7 @@ private static void findNearestLinkForZonesWithoutLinks(Network networkToChange, private TripDistributionMatrix createTripDistribution( Map> trafficVolume_start, Map> trafficVolume_stop, - String smallScaleCommercialTrafficType, Scenario scenario, Path output, Map, Link>> regionLinksMap) + String smallScaleCommercialTrafficType, Scenario scenario, Path output, Map, Link>> linksPerZone) throws Exception { ArrayList listOfZones = new ArrayList<>(); @@ -994,7 +976,7 @@ private TripDistributionMatrix createTripDistribution( Collections.shuffle(listOfZones, rnd); for (String stopZone : listOfZones) { odMatrix.setTripDistributionValue(startZone, stopZone, modeORvehType, purpose, smallScaleCommercialTrafficType, - network, regionLinksMap, resistanceFactor, shapeFileZoneNameColumn); + network, linksPerZone, resistanceFactor, shapeFileZoneNameColumn); } } } diff --git a/contribs/small-scale-traffic-generation/src/main/java/org/matsim/smallScaleCommercialTrafficGeneration/SmallScaleCommercialTrafficUtils.java b/contribs/small-scale-traffic-generation/src/main/java/org/matsim/smallScaleCommercialTrafficGeneration/SmallScaleCommercialTrafficUtils.java index f00dcd020e5..965b8387228 100644 --- a/contribs/small-scale-traffic-generation/src/main/java/org/matsim/smallScaleCommercialTrafficGeneration/SmallScaleCommercialTrafficUtils.java +++ b/contribs/small-scale-traffic-generation/src/main/java/org/matsim/smallScaleCommercialTrafficGeneration/SmallScaleCommercialTrafficUtils.java @@ -19,7 +19,6 @@ * *********************************************************************** */ package org.matsim.smallScaleCommercialTrafficGeneration; -import com.google.common.base.Joiner; import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem; import com.graphhopper.jsprit.core.problem.job.Job; import com.graphhopper.jsprit.core.problem.solution.SolutionCostCalculator; @@ -28,6 +27,7 @@ import com.graphhopper.jsprit.core.problem.solution.route.activity.BreakActivity; import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity; import it.unimi.dsi.fastutil.objects.Object2DoubleMap; +import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; @@ -40,24 +40,23 @@ import org.matsim.api.core.v01.population.*; import org.matsim.application.options.ShpOptions; import org.matsim.application.options.ShpOptions.Index; +import org.matsim.core.gbl.MatsimRandom; +import org.matsim.core.network.NetworkUtils; +import org.matsim.core.population.PopulationUtils; +import org.matsim.core.utils.io.IOUtils; import org.matsim.freight.carriers.*; import org.matsim.freight.carriers.CarrierCapabilities.FleetSize; import org.matsim.freight.carriers.Tour.Pickup; import org.matsim.freight.carriers.Tour.ServiceActivity; import org.matsim.freight.carriers.Tour.TourElement; import org.matsim.freight.carriers.jsprit.MatsimJspritFactory; -import org.matsim.core.gbl.MatsimRandom; -import org.matsim.core.network.NetworkUtils; -import org.matsim.core.population.PopulationUtils; -import org.matsim.core.utils.io.IOUtils; import org.matsim.vehicles.Vehicle; import org.matsim.vehicles.VehicleType; import org.matsim.vehicles.VehicleUtils; import org.matsim.vehicles.Vehicles; -import java.io.BufferedWriter; +import java.io.BufferedReader; import java.io.IOException; -import java.net.MalformedURLException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -72,7 +71,6 @@ public class SmallScaleCommercialTrafficUtils { private static final Logger log = LogManager.getLogger(SmallScaleCommercialTrafficUtils.class); - private static final Joiner JOIN = Joiner.on("\t"); /** * Creates and return the Index of the zone shape. @@ -82,7 +80,7 @@ public class SmallScaleCommercialTrafficUtils { * @param shapeFileZoneNameColumn Column name of the zone in the shape file * @return indexZones */ - static Index getIndexZones(Path shapeFileZonePath, String shapeCRS, String shapeFileZoneNameColumn) { + public static Index getIndexZones(Path shapeFileZonePath, String shapeCRS, String shapeFileZoneNameColumn) { ShpOptions shpZones = new ShpOptions(shapeFileZonePath, shapeCRS, StandardCharsets.UTF_8); if (shpZones.readFeatures().iterator().next().getAttribute(shapeFileZoneNameColumn) == null) @@ -98,7 +96,7 @@ static Index getIndexZones(Path shapeFileZonePath, String shapeCRS, String shape * @param shapeFileLanduseTypeColumn Column name of the landuse in the shape file * @return indexLanduse */ - static Index getIndexLanduse(Path shapeFileLandusePath, String shapeCRS, String shapeFileLanduseTypeColumn) { + public static Index getIndexLanduse(Path shapeFileLandusePath, String shapeCRS, String shapeFileLanduseTypeColumn) { ShpOptions shpLanduse = new ShpOptions(shapeFileLandusePath, shapeCRS, StandardCharsets.UTF_8); if (shpLanduse.readFeatures().iterator().next().getAttribute(shapeFileLanduseTypeColumn) == null) throw new NullPointerException("The column '" + shapeFileLanduseTypeColumn + "' does not exist in the landuse shape file. Please check the input."); @@ -113,7 +111,7 @@ static Index getIndexLanduse(Path shapeFileLandusePath, String shapeCRS, String * @param shapeFileBuildingTypeColumn Column name of the building in the shape file * @return indexBuildings */ - static Index getIndexBuildings(Path shapeFileBuildingsPath, String shapeCRS, String shapeFileBuildingTypeColumn) { + public static Index getIndexBuildings(Path shapeFileBuildingsPath, String shapeCRS, String shapeFileBuildingTypeColumn) { ShpOptions shpBuildings = new ShpOptions(shapeFileBuildingsPath, shapeCRS, StandardCharsets.UTF_8); if (shpBuildings.readFeatures().iterator().next().getAttribute(shapeFileBuildingTypeColumn) == null) throw new NullPointerException("The column '" + shapeFileBuildingTypeColumn + "' does not exist in the building shape file. Please check the input."); @@ -136,17 +134,6 @@ public static Index getIndexRegions(Path shapeFileRegionsPath, String shapeCRS, return shpRegions.createIndex(shapeCRS, regionsShapeRegionColumn); } - /** - * Writes a csv file with the result of the distribution per zone of the input data. - */ - static void writeResultOfDataDistribution(Map> resultingDataPerZone, - Path outputFileInOutputFolder, Map zoneIdRegionConnection) - throws IOException { - - writeCSVWithCategoryHeader(resultingDataPerZone, outputFileInOutputFolder, zoneIdRegionConnection); - log.info("The data distribution is finished and written to: " + outputFileInOutputFolder); - } - /** Finds the nearest possible link for the building polygon. * @param zone * @param noPossibleLinks @@ -191,39 +178,6 @@ static Id findNearestPossibleLink(String zone, List noPossibleLink return newLink; } - /** - * Writer of data distribution data. - */ - private static void writeCSVWithCategoryHeader(Map> resultingDataPerZone, - Path outputFileInInputFolder, - Map zoneIdRegionConnection) throws MalformedURLException { - BufferedWriter writer = IOUtils.getBufferedWriter(outputFileInInputFolder.toUri().toURL(), - StandardCharsets.UTF_8, true); - try { - String[] header = new String[]{"zoneID", "region", "Inhabitants", "Employee", "Employee Primary Sector", - "Employee Construction", "Employee Secondary Sector Rest", "Employee Retail", - "Employee Traffic/Parcels", "Employee Tertiary Sector Rest"}; - JOIN.appendTo(writer, header); - writer.write("\n"); - for (String zone : resultingDataPerZone.keySet()) { - List row = new ArrayList<>(); - row.add(zone); - row.add(zoneIdRegionConnection.get(zone)); - for (String category : header) { - if (!category.equals("zoneID") && !category.equals("region")) - row.add(String.valueOf((int) Math.round(resultingDataPerZone.get(zone).getDouble(category)))); - } - JOIN.appendTo(writer, row); - writer.write("\n"); - } - - writer.close(); - - } catch (IOException e) { - log.error("Could not write the csv file with the data distribution data.", e); - } - } - /** * Creates a population including the plans in preparation for the MATSim run. If a different name of the population is set, different plan variants per person are created */ @@ -571,6 +525,37 @@ static String findZoneOfLink(Id linkId, Map, Link>> l return null; } + + /** TODO + * @param pathToDataDistributionToZones + * @return + * @throws IOException + */ + static Map> readDataDistribution(Path pathToDataDistributionToZones) throws IOException { + if (!Files.exists(pathToDataDistributionToZones)) { + log.error("Required data per zone file {} not found", pathToDataDistributionToZones); + } + + Map> resultingDataPerZone = new HashMap<>(); + try (BufferedReader reader = IOUtils.getBufferedReader(pathToDataDistributionToZones.toString())) { + CSVParser parse = CSVFormat.Builder.create(CSVFormat.DEFAULT).setDelimiter('\t').setHeader() + .setSkipHeaderRecord(true).build().parse(reader); + + for (CSVRecord record : parse) { + String zoneID = record.get("zoneID"); + resultingDataPerZone.put(zoneID, new Object2DoubleOpenHashMap<>()); + for (int n = 2; n < parse.getHeaderMap().size(); n++) { + resultingDataPerZone.get(zoneID).mergeDouble(parse.getHeaderNames().get(n), + Double.parseDouble(record.get(n)), Double::sum); + } + } + } + log.info("Data distribution for " + resultingDataPerZone.size() + " zones was read from " + + pathToDataDistributionToZones); + return resultingDataPerZone; + + } + /** * Creates a cost calculator. */ diff --git a/contribs/small-scale-traffic-generation/src/main/java/org/matsim/smallScaleCommercialTrafficGeneration/prepare/CreateDataDistributionOfStructureData.java b/contribs/small-scale-traffic-generation/src/main/java/org/matsim/smallScaleCommercialTrafficGeneration/prepare/CreateDataDistributionOfStructureData.java new file mode 100644 index 00000000000..07255fc367f --- /dev/null +++ b/contribs/small-scale-traffic-generation/src/main/java/org/matsim/smallScaleCommercialTrafficGeneration/prepare/CreateDataDistributionOfStructureData.java @@ -0,0 +1,151 @@ +package org.matsim.smallScaleCommercialTrafficGeneration.prepare; + +import it.unimi.dsi.fastutil.objects.Object2DoubleMap; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.locationtech.jts.geom.Geometry; +import org.matsim.api.core.v01.Coord; +import org.matsim.api.core.v01.Id; +import org.matsim.application.MATSimAppCommand; +import org.matsim.application.options.ShpOptions; +import org.matsim.core.utils.geometry.geotools.MGC; +import org.matsim.facilities.*; +import org.matsim.smallScaleCommercialTrafficGeneration.SmallScaleCommercialTrafficUtils; +import org.opengis.feature.simple.SimpleFeature; +import picocli.CommandLine; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.matsim.smallScaleCommercialTrafficGeneration.prepare.LanduseBuildingAnalysis.*; + +@CommandLine.Command(name = "create-data-distribution-of-structure-data", description = "Create data distribution as preperation for the generation of small scale commercial traffic", showDefaultValues = true) +public class CreateDataDistributionOfStructureData implements MATSimAppCommand { + + private static final Logger log = LogManager.getLogger(CreateDataDistributionOfStructureData.class); + + private enum LanduseConfiguration { + useOnlyOSMLanduse, useOSMBuildingsAndLanduse + } + + @CommandLine.Option(names = "--pathOutput", description = "Path for the output", defaultValue = "output/TestDistributionClass") + private Path output; + + @CommandLine.Option(names = "--landuseConfiguration", description = "Set option of used OSM data. Options: useOnlyOSMLanduse, useOSMBuildingsAndLanduse, useExistingDataDistribution", defaultValue = "useOSMBuildingsAndLanduse") + private LanduseConfiguration usedLanduseConfiguration; + + @CommandLine.Option(names = "--regionsShapeFileName", description = "Path of the region shape file.", defaultValue = "contribs/small-scale-traffic-generation/test/input/org/matsim/smallScaleCommercialTrafficGeneration/shp/testRegions.shp") + private Path shapeFileRegionsPath; + + @CommandLine.Option(names = "--regionsShapeRegionColumn", description = "Name of the region column in the region shape file.", defaultValue = "region") + private String regionsShapeRegionColumn; + + @CommandLine.Option(names = "--zoneShapeFileName", description = "Path of the zone shape file.", defaultValue = "contribs/small-scale-traffic-generation/test/input/org/matsim/smallScaleCommercialTrafficGeneration/shp/testZones.shp") + private Path shapeFileZonePath; + + @CommandLine.Option(names = "--zoneShapeFileNameColumn", description = "Name of the unique column of the name/Id of each zone in the zones shape file.", defaultValue = "name") + private String shapeFileZoneNameColumn; + + @CommandLine.Option(names = "--buildingsShapeFileName", description = "Path of the buildings shape file", defaultValue = "contribs/small-scale-traffic-generation/test/input/org/matsim/smallScaleCommercialTrafficGeneration/shp/testBuildings.shp") + private Path shapeFileBuildingsPath; + + @CommandLine.Option(names = "--shapeFileBuildingTypeColumn", description = "Name of the unique column of the building type in the buildings shape file.", defaultValue = "type") + private String shapeFileBuildingTypeColumn; + + @CommandLine.Option(names = "--landuseShapeFileName", description = "Path of the landuse shape file", defaultValue = "contribs/small-scale-traffic-generation/test/input/org/matsim/smallScaleCommercialTrafficGeneration/shp/testLanduse.shp") + private Path shapeFileLandusePath; + + @CommandLine.Option(names = "--shapeFileLanduseTypeColumn", description = "Name of the unique column of the landuse type in the landuse shape file.", defaultValue = "fclass") + private String shapeFileLanduseTypeColumn; + + @CommandLine.Option(names = "--shapeCRS", description = "CRS of the three input shape files (zones, landuse, buildings", defaultValue = "EPSG:4326") + private String shapeCRS; + + @CommandLine.Option(names = "--pathToInvestigationAreaData", description = "Path to the investigation area data", defaultValue = "contribs/small-scale-traffic-generation/test/input/org/matsim/smallScaleCommercialTrafficGeneration/investigationAreaData.csv") + private Path pathToInvestigationAreaData; + + private final Map> landuseCategoriesAndDataConnection = new HashMap<>(); + private final Map>> buildingsPerZone = new HashMap<>(); + + private ShpOptions.Index indexZones; + private ShpOptions.Index indexBuildings; + private ShpOptions.Index indexLanduse; + private ShpOptions.Index indexInvestigationAreaRegions; + + public static void main(String[] args) { + System.exit(new CommandLine(new CreateDataDistributionOfStructureData()).execute(args)); + } + + @Override + public Integer call() throws Exception { + log.info("Create data distribution of structure data"); + + if (!Files.exists(shapeFileLandusePath)) { + throw new Exception("Required landuse shape file not found:" + shapeFileLandusePath.toString()); + } + if (!Files.exists(shapeFileBuildingsPath)) { + throw new Exception( + "Required OSM buildings shape file {} not found" + shapeFileBuildingsPath.toString()); + } + if (!Files.exists(shapeFileZonePath)) { + throw new Exception("Required districts shape file {} not found" + shapeFileZonePath.toString()); + } + if (!Files.exists(shapeFileRegionsPath)) { + throw new Exception("Required regions shape file {} not found" + shapeFileRegionsPath.toString()); + } + + indexZones = SmallScaleCommercialTrafficUtils.getIndexZones(shapeFileZonePath, shapeCRS, shapeFileZoneNameColumn); + indexBuildings = SmallScaleCommercialTrafficUtils.getIndexBuildings(shapeFileBuildingsPath, shapeCRS, shapeFileBuildingTypeColumn); + indexLanduse = SmallScaleCommercialTrafficUtils.getIndexLanduse(shapeFileLandusePath, shapeCRS, shapeFileLanduseTypeColumn); + indexInvestigationAreaRegions = SmallScaleCommercialTrafficUtils.getIndexRegions(shapeFileRegionsPath, shapeCRS, regionsShapeRegionColumn); + + if(Files.notExists(output)) + new File(output.toString()).mkdir(); + + createDefaultDataConnectionForOSM(landuseCategoriesAndDataConnection); //TODO: find way to import this connection + + Map> resultingDataPerZone = LanduseBuildingAnalysis + .createInputDataDistribution(output, landuseCategoriesAndDataConnection, + usedLanduseConfiguration.toString(), indexLanduse, indexZones, + indexBuildings, indexInvestigationAreaRegions, shapeFileZoneNameColumn, buildingsPerZone, pathToInvestigationAreaData); + + ActivityFacilities facilities = FacilitiesUtils.createActivityFacilities(); + + ActivityFacilitiesFactory f = facilities.getFactory(); + + for (String zone : buildingsPerZone.keySet()) { + for (String assignedDataType : buildingsPerZone.get(zone).keySet()) { + buildingsPerZone.get(zone).get(assignedDataType).forEach(singleBuilding -> { + Id id = Id.create(singleBuilding.getID(), ActivityFacility.class); + if (facilities.getFacilities().containsKey(id)) { + facilities.getFacilities().get(id).addActivityOption(f.createActivityOption(assignedDataType)); + } else { + Coord coord = MGC.point2Coord(((Geometry) singleBuilding.getDefaultGeometry()).getCentroid()); + ActivityFacility facility = f.createActivityFacility(id, coord); + facility.addActivityOption(f.createActivityOption(assignedDataType)); + String[] buildingTypes = ((String) singleBuilding.getAttribute("type")).split(";"); + int calculatedAreaPerBuildingCategory = calculateAreaPerBuildingCategory(singleBuilding, buildingTypes); + facility.getAttributes().putAttribute("shareOfZone_" + assignedDataType, + getShareOfTheBuildingAreaOfTheRelatedAreaOfTheZone(zone, calculatedAreaPerBuildingCategory, + assignedDataType)); //TODO: check if this is the right + //TODO Employee hat momentan noch infinity als Wert + facility.getAttributes().putAttribute("zone", zone); + facilities.addActivityFacility(facility); + } + }); + + } + } + Path facilityOutput = output.resolve("commercialFacilities.xml.gz"); + log.info("Created {} facilities, writing to {}", facilities.getFacilities().size(), facilityOutput); + + FacilitiesWriter writer = new FacilitiesWriter(facilities); + writer.write(facilityOutput.toString()); + + return 0; + } +} diff --git a/contribs/small-scale-traffic-generation/src/main/java/org/matsim/smallScaleCommercialTrafficGeneration/LanduseBuildingAnalysis.java b/contribs/small-scale-traffic-generation/src/main/java/org/matsim/smallScaleCommercialTrafficGeneration/prepare/LanduseBuildingAnalysis.java similarity index 70% rename from contribs/small-scale-traffic-generation/src/main/java/org/matsim/smallScaleCommercialTrafficGeneration/LanduseBuildingAnalysis.java rename to contribs/small-scale-traffic-generation/src/main/java/org/matsim/smallScaleCommercialTrafficGeneration/prepare/LanduseBuildingAnalysis.java index 7e26668438e..b9931fbbfbb 100644 --- a/contribs/small-scale-traffic-generation/src/main/java/org/matsim/smallScaleCommercialTrafficGeneration/LanduseBuildingAnalysis.java +++ b/contribs/small-scale-traffic-generation/src/main/java/org/matsim/smallScaleCommercialTrafficGeneration/prepare/LanduseBuildingAnalysis.java @@ -17,8 +17,9 @@ * See also COPYING, LICENSE and WARRANTY file * * * * *********************************************************************** */ -package org.matsim.smallScaleCommercialTrafficGeneration; +package org.matsim.smallScaleCommercialTrafficGeneration.prepare; +import com.google.common.base.Joiner; import it.unimi.dsi.fastutil.objects.Object2DoubleMap; import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap; import org.apache.commons.csv.CSVFormat; @@ -34,11 +35,12 @@ import org.matsim.core.utils.io.IOUtils; import org.opengis.feature.simple.SimpleFeature; -import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.IOException; +import java.net.MalformedURLException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; import java.util.*; /** @@ -48,87 +50,66 @@ public class LanduseBuildingAnalysis { private static final Logger log = LogManager.getLogger(LanduseBuildingAnalysis.class); + private static final Joiner JOIN = Joiner.on("\t"); + static Map> resultingSumsForZones = new HashMap<>(); /** * Creates a distribution of the given input data for each zone based on the * used OSM data. */ - static Map> createInputDataDistribution(Path output, - Map> landuseCategoriesAndDataConnection, - String usedLanduseConfiguration, Index indexLanduse, Index indexZones, - Index indexBuildings, Index indexInvestigationAreaRegions, - String shapeFileZoneNameColumn, - Map>> buildingsPerZone, - Path pathToInvestigationAreaData, - Path pathToExistingDataDistributionToZones) + public static Map> createInputDataDistribution(Path output, + Map> landuseCategoriesAndDataConnection, + String usedLanduseConfiguration, Index indexLanduse, + Index indexZones, + Index indexBuildings, Index indexInvestigationAreaRegions, + String shapeFileZoneNameColumn, + Map>> buildingsPerZone, + Path pathToInvestigationAreaData) throws IOException { Map> resultingDataPerZone = new HashMap<>(); Map zoneIdRegionConnection = new HashMap<>(); - Path outputFileInOutputFolder = output.resolve("calculatedData").resolve("dataDistributionPerZone.csv"); + // aufgrund des separaten Aufbaus der Ordnerstruktur wird der Pfad angepasst +// Path outputFileInOutputFolder = output.resolve("calculatedData").resolve("dataDistributionPerZone.csv"); + Path outputFileInOutputFolder = output.resolve("dataDistributionPerZone.csv"); - landuseCategoriesAndDataConnection.put("Inhabitants", - new ArrayList(Arrays.asList("residential", "apartments", "dormitory", "dwelling_house", "house", - "retirement_home", "semidetached_house", "detached"))); - landuseCategoriesAndDataConnection.put("Employee Primary Sector", new ArrayList( - Arrays.asList("farmyard", "farmland", "farm", "farm_auxiliary", "greenhouse", "agricultural"))); - landuseCategoriesAndDataConnection.put("Employee Construction", - new ArrayList(List.of("construction"))); - landuseCategoriesAndDataConnection.put("Employee Secondary Sector Rest", - new ArrayList(Arrays.asList("industrial", "factory", "manufacture", "bakehouse"))); - landuseCategoriesAndDataConnection.put("Employee Retail", - new ArrayList(Arrays.asList("retail", "kiosk", "mall", "shop", "supermarket"))); - landuseCategoriesAndDataConnection.put("Employee Traffic/Parcels", new ArrayList( - Arrays.asList("commercial", "post_office", "storage", "storage_tank", "warehouse"))); - landuseCategoriesAndDataConnection.put("Employee Tertiary Sector Rest", new ArrayList( - Arrays.asList("commercial", "embassy", "foundation", "government", "office", "townhall"))); - - if (usedLanduseConfiguration.equals("useExistingDataDistribution")) { - - if (!Files.exists(pathToExistingDataDistributionToZones)) { - log.error("Required data per zone file {} not found", pathToExistingDataDistributionToZones); - } + log.info("New analyze for data distribution is started. The used method is: " + usedLanduseConfiguration); + Map> landuseCategoriesPerZone = new HashMap<>(); + createLanduseDistribution(landuseCategoriesPerZone, indexLanduse, indexZones, indexInvestigationAreaRegions, + usedLanduseConfiguration, indexBuildings, landuseCategoriesAndDataConnection, + buildingsPerZone, shapeFileZoneNameColumn, zoneIdRegionConnection); - try (BufferedReader reader = IOUtils.getBufferedReader(pathToExistingDataDistributionToZones.toString())) { - CSVParser parse = CSVFormat.Builder.create(CSVFormat.DEFAULT).setDelimiter('\t').setHeader() - .setSkipHeaderRecord(true).build().parse(reader); + Map> investigationAreaData = new HashMap<>(); + readAreaData(investigationAreaData, pathToInvestigationAreaData); - for (CSVRecord record : parse) { - String zoneID = record.get("zoneID"); - resultingDataPerZone.put(zoneID, new Object2DoubleOpenHashMap<>()); - for (int n = 2; n < parse.getHeaderMap().size(); n++) { - resultingDataPerZone.get(zoneID).mergeDouble(parse.getHeaderNames().get(n), - Double.parseDouble(record.get(n)), Double::sum); - } - } - } - log.info("Data distribution for " + resultingDataPerZone.size() + " zones was imported from " + - pathToExistingDataDistributionToZones); - Files.copy(pathToExistingDataDistributionToZones, outputFileInOutputFolder, StandardCopyOption.COPY_ATTRIBUTES); - } - - else { + createResultingDataForLanduseInZones(landuseCategoriesPerZone, investigationAreaData, resultingDataPerZone, + landuseCategoriesAndDataConnection, zoneIdRegionConnection); - log.info("New analyze for data distribution is started. The used method is: " + usedLanduseConfiguration); + writeResultOfDataDistribution(resultingDataPerZone, outputFileInOutputFolder, + zoneIdRegionConnection); - Map> landuseCategoriesPerZone = new HashMap<>(); - createLanduseDistribution(landuseCategoriesPerZone, indexLanduse, indexZones, indexInvestigationAreaRegions, - usedLanduseConfiguration, indexBuildings, landuseCategoriesAndDataConnection, - buildingsPerZone, shapeFileZoneNameColumn, zoneIdRegionConnection); - - Map> investigationAreaData = new HashMap<>(); - readAreaData(investigationAreaData, pathToInvestigationAreaData); - - createResultingDataForLanduseInZones(landuseCategoriesPerZone, investigationAreaData, resultingDataPerZone, - landuseCategoriesAndDataConnection, zoneIdRegionConnection); - - SmallScaleCommercialTrafficUtils.writeResultOfDataDistribution(resultingDataPerZone, outputFileInOutputFolder, - zoneIdRegionConnection); - } return resultingDataPerZone; } + public static void createDefaultDataConnectionForOSM(Map> landuseCategoriesAndDataConnection) { + landuseCategoriesAndDataConnection.put("Inhabitants", + new ArrayList<>(Arrays.asList("residential", "apartments", "dormitory", "dwelling_house", "house", + "retirement_home", "semidetached_house", "detached"))); + landuseCategoriesAndDataConnection.put("Employee Primary Sector", new ArrayList<>( + Arrays.asList("farmyard", "farmland", "farm", "farm_auxiliary", "greenhouse", "agricultural"))); + landuseCategoriesAndDataConnection.put("Employee Construction", + new ArrayList<>(List.of("construction"))); + landuseCategoriesAndDataConnection.put("Employee Secondary Sector Rest", + new ArrayList<>(Arrays.asList("industrial", "factory", "manufacture", "bakehouse"))); + landuseCategoriesAndDataConnection.put("Employee Retail", + new ArrayList<>(Arrays.asList("retail", "kiosk", "mall", "shop", "supermarket"))); + landuseCategoriesAndDataConnection.put("Employee Traffic/Parcels", new ArrayList<>( + Arrays.asList("commercial", "post_office", "storage", "storage_tank", "warehouse"))); + landuseCategoriesAndDataConnection.put("Employee Tertiary Sector Rest", new ArrayList<>( + Arrays.asList("commercial", "embassy", "foundation", "government", "office", "townhall"))); + } + /** * Creates the resulting data for each zone based on the landuse distribution * and the original data. @@ -169,7 +150,9 @@ private static void createResultingDataForLanduseInZones( } } } - + for (Map.Entry> entry : resultingDataPerZone.entrySet()) { + resultingSumsForZones.put(entry.getKey(), new Object2DoubleOpenHashMap<>(entry.getValue())); + } /* * creates the percentages of each category and zones based on the sum in this * category @@ -257,13 +240,7 @@ private static void createLanduseDistribution(Map buildingsFeatures, if (singleZone != null) { categoriesOfBuilding.forEach(c -> buildingsPerZone .computeIfAbsent(singleZone, k -> new HashMap<>()) - .computeIfAbsent(c, k -> new ArrayList()).add(singleBuildingFeature)); + .computeIfAbsent(c, k -> new ArrayList<>()).add(singleBuildingFeature)); } } log.info("Finished analyzing buildings types."); } + + + /** + * Writes a csv file with the result of the distribution per zone of the input data. + */ + private static void writeResultOfDataDistribution(Map> resultingDataPerZone, + Path outputFileInOutputFolder, Map zoneIdRegionConnection) + throws IOException { + + writeCSVWithCategoryHeader(resultingDataPerZone, outputFileInOutputFolder, zoneIdRegionConnection); + log.info("The data distribution is finished and written to: " + outputFileInOutputFolder); + } + + /** + * Writer of data distribution data. + */ + private static void writeCSVWithCategoryHeader(Map> resultingDataPerZone, + Path outputFileInInputFolder, + Map zoneIdRegionConnection) throws MalformedURLException { + BufferedWriter writer = IOUtils.getBufferedWriter(outputFileInInputFolder.toUri().toURL(), + StandardCharsets.UTF_8, true); + try { + String[] header = new String[]{"zoneID", "region", "Inhabitants", "Employee", "Employee Primary Sector", + "Employee Construction", "Employee Secondary Sector Rest", "Employee Retail", + "Employee Traffic/Parcels", "Employee Tertiary Sector Rest"}; + JOIN.appendTo(writer, header); + writer.write("\n"); + for (String zone : resultingDataPerZone.keySet()) { + List row = new ArrayList<>(); + row.add(zone); + row.add(zoneIdRegionConnection.get(zone)); + for (String category : header) { + if (!category.equals("zoneID") && !category.equals("region")) + row.add(String.valueOf((int) Math.round(resultingDataPerZone.get(zone).getDouble(category)))); + } + JOIN.appendTo(writer, row); + writer.write("\n"); + } + + writer.close(); + + } catch (IOException e) { + log.error("Could not write the csv file with the data distribution data.", e); + } + } + + static double getShareOfTheBuildingAreaOfTheRelatedAreaOfTheZone(String zone, int area, String category){ + return area / resultingSumsForZones.get(zone).getDouble(category); + } } diff --git a/contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/SCTUtils.java b/contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/SCTUtils.java index 843a690196d..87319ceaac0 100644 --- a/contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/SCTUtils.java +++ b/contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/SCTUtils.java @@ -9,22 +9,22 @@ */ public class SCTUtils { - static ShpOptions.Index getZoneIndex(Path inputDataDirectory) { + public static ShpOptions.Index getZoneIndex(Path inputDataDirectory) { Path shapeFileZonePath = inputDataDirectory.resolve("shp/testZones.shp"); return new ShpOptions(shapeFileZonePath, null, null).createIndex("name"); } - static ShpOptions.Index getIndexLanduse(Path inputDataDirectory) { + public static ShpOptions.Index getIndexLanduse(Path inputDataDirectory) { Path shapeFileLandusePath = inputDataDirectory.resolve("shp/testLanduse.shp"); return new ShpOptions(shapeFileLandusePath, null, null).createIndex("fclass"); } - static ShpOptions.Index getIndexBuildings(Path inputDataDirectory) { + public static ShpOptions.Index getIndexBuildings(Path inputDataDirectory) { Path shapeFileBuildingsPath = inputDataDirectory.resolve("shp/testBuildings.shp"); return new ShpOptions(shapeFileBuildingsPath, null, null).createIndex("type"); } - static ShpOptions.Index getIndexRegions(Path inputDataDirectory) { + public static ShpOptions.Index getIndexRegions(Path inputDataDirectory) { Path shapeFileRegionsPath = inputDataDirectory.resolve("shp/testRegions.shp"); return new ShpOptions(shapeFileRegionsPath, null, null).createIndex("region"); } diff --git a/contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/SmallScaleCommercialTrafficUtilsTest.java b/contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/SmallScaleCommercialTrafficUtilsTest.java index 5aa06d111fc..3a564be62a8 100644 --- a/contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/SmallScaleCommercialTrafficUtilsTest.java +++ b/contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/SmallScaleCommercialTrafficUtilsTest.java @@ -28,8 +28,8 @@ import org.matsim.core.config.Config; import org.matsim.core.config.ConfigUtils; import org.matsim.core.scenario.ScenarioUtils; +import org.matsim.facilities.ActivityFacility; import org.matsim.testcases.MatsimTestUtils; -import org.opengis.feature.simple.SimpleFeature; import java.io.IOException; import java.net.URISyntaxException; @@ -58,13 +58,12 @@ void findZoneOfLinksTest() throws IOException, URISyntaxException { config.network().setInputFile(networkPath); config.network().setInputCRS("EPSG:4326"); Scenario scenario = ScenarioUtils.loadScenario(config); - Map>> buildingsPerZone = new HashMap<>(); + Map>> facilitiesPerZone = new HashMap<>(); String shapeFileZoneNameColumn = "name"; - Map, Link>> regionLinksMap = GenerateSmallScaleCommercialTrafficDemand - .filterLinksForZones(scenario, SmallScaleCommercialTrafficUtils.getIndexZones(shapeFileZonePath, config.global().getCoordinateSystem(), - shapeFileZoneNameColumn), - buildingsPerZone); + Map, Link>> regionLinksMap = GenerateSmallScaleCommercialTrafficDemand.filterLinksForZones(scenario, + SmallScaleCommercialTrafficUtils.getIndexZones(shapeFileZonePath, config.global().getCoordinateSystem(), shapeFileZoneNameColumn), + facilitiesPerZone); Assertions.assertEquals(3, regionLinksMap.size(), MatsimTestUtils.EPSILON); Assertions.assertEquals(60, regionLinksMap.get("area1").size(), MatsimTestUtils.EPSILON); diff --git a/contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/TrafficVolumeGenerationTest.java b/contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/TrafficVolumeGenerationTest.java index b680e568ffb..a11a5df9bd4 100644 --- a/contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/TrafficVolumeGenerationTest.java +++ b/contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/TrafficVolumeGenerationTest.java @@ -29,10 +29,12 @@ import org.matsim.core.config.Config; import org.matsim.core.config.ConfigUtils; import org.matsim.core.scenario.ScenarioUtils; +import org.matsim.facilities.ActivityFacility; import org.matsim.freight.carriers.Carrier; import org.matsim.freight.carriers.CarrierCapabilities.FleetSize; import org.matsim.freight.carriers.CarriersUtils; import org.matsim.smallScaleCommercialTrafficGeneration.TrafficVolumeGeneration.TrafficVolumeKey; +import org.matsim.smallScaleCommercialTrafficGeneration.prepare.LanduseBuildingAnalysis; import org.matsim.testcases.MatsimTestUtils; import org.opengis.feature.simple.SimpleFeature; @@ -41,6 +43,8 @@ import java.nio.file.Path; import java.util.*; +import static org.matsim.smallScaleCommercialTrafficGeneration.prepare.LanduseBuildingAnalysis.createDefaultDataConnectionForOSM; + /** * @author Ricardo Ewert * @@ -59,16 +63,16 @@ void testTrafficVolumeGenerationCommercialPersonTraffic() throws IOException { Path output = Path.of(utils.getOutputDirectory()); assert(new File(output.resolve("calculatedData").toString()).mkdir()); Path inputDataDirectory = Path.of(utils.getPackageInputDirectory()); - String usedLanduseConfiguration = "useExistingDataDistribution"; + String usedLanduseConfiguration = "useOSMBuildingsAndLanduse"; String shapeFileZoneNameColumn = "name"; Path pathToInvestigationAreaData = Path.of(utils.getPackageInputDirectory()).resolve("investigationAreaData.csv"); - Path pathToExistingDataDistributionToZones = Path.of(utils.getPackageInputDirectory()).resolve("dataDistributionPerZone.csv"); + createDefaultDataConnectionForOSM(landuseCategoriesAndDataConnection); Map> resultingDataPerZone = LanduseBuildingAnalysis .createInputDataDistribution(output, landuseCategoriesAndDataConnection, usedLanduseConfiguration, SCTUtils.getIndexLanduse(inputDataDirectory), SCTUtils.getZoneIndex(inputDataDirectory), SCTUtils.getIndexBuildings(inputDataDirectory), - SCTUtils.getIndexRegions(inputDataDirectory), shapeFileZoneNameColumn, buildingsPerZone, pathToInvestigationAreaData, pathToExistingDataDistributionToZones); + SCTUtils.getIndexRegions(inputDataDirectory), shapeFileZoneNameColumn, buildingsPerZone, pathToInvestigationAreaData); String usedTrafficType = "commercialPersonTraffic"; @@ -189,16 +193,16 @@ void testTrafficVolumeGenerationGoodsTraffic() throws IOException { Path output = Path.of(utils.getOutputDirectory()); assert(new File(output.resolve("calculatedData").toString()).mkdir()); Path inputDataDirectory = Path.of(utils.getPackageInputDirectory()); - String usedLanduseConfiguration = "useExistingDataDistribution"; + String usedLanduseConfiguration = "useOSMBuildingsAndLanduse"; String shapeFileZoneNameColumn = "name"; Path pathToInvestigationAreaData = Path.of(utils.getPackageInputDirectory()).resolve("investigationAreaData.csv"); - Path pathToExistingDataDistributionToZones = Path.of(utils.getPackageInputDirectory()).resolve("dataDistributionPerZone.csv"); + createDefaultDataConnectionForOSM(landuseCategoriesAndDataConnection); Map> resultingDataPerZone = LanduseBuildingAnalysis .createInputDataDistribution(output, landuseCategoriesAndDataConnection, usedLanduseConfiguration, SCTUtils.getIndexLanduse(inputDataDirectory), SCTUtils.getZoneIndex(inputDataDirectory), SCTUtils.getIndexBuildings(inputDataDirectory), - SCTUtils.getIndexRegions(inputDataDirectory), shapeFileZoneNameColumn, buildingsPerZone, pathToInvestigationAreaData, pathToExistingDataDistributionToZones); + SCTUtils.getIndexRegions(inputDataDirectory), shapeFileZoneNameColumn, buildingsPerZone, pathToInvestigationAreaData); String usedTrafficType = "goodsTraffic"; double sample = 1.; @@ -395,15 +399,14 @@ void testAddingExistingScenarios() throws Exception { config.network().setInputCRS("EPSG:4326"); config.setContext(inputDataDirectory.resolve("config.xml").toUri().toURL()); Scenario scenario = ScenarioUtils.loadScenario(config); - Map>> buildingsPerZone = new HashMap<>(); + Map>> facilitiesPerZone = new HashMap<>(); String shapeFileZoneNameColumn = "name"; - Map, Link>> regionLinksMap = GenerateSmallScaleCommercialTrafficDemand - .filterLinksForZones(scenario, SmallScaleCommercialTrafficUtils.getIndexZones(shapeFileZonePath, config.global().getCoordinateSystem(), - shapeFileZoneNameColumn), - buildingsPerZone); + Map, Link>> linksPerZone = GenerateSmallScaleCommercialTrafficDemand.filterLinksForZones(scenario, + SmallScaleCommercialTrafficUtils.getIndexZones(shapeFileZonePath, config.global().getCoordinateSystem(), shapeFileZoneNameColumn), + facilitiesPerZone); - SmallScaleCommercialTrafficUtils.readExistingModels(scenario, sample, regionLinksMap); + SmallScaleCommercialTrafficUtils.readExistingModels(scenario, sample, linksPerZone); Assertions.assertEquals(3, CarriersUtils.getCarriers(scenario).getCarriers().size(), MatsimTestUtils.EPSILON); Assertions.assertEquals(1, CarriersUtils.getCarrierVehicleTypes(scenario).getVehicleTypes().size(), MatsimTestUtils.EPSILON); @@ -465,13 +468,13 @@ void testAddingExistingScenariosWithSample() throws Exception { config.network().setInputCRS("EPSG:4326"); config.setContext(inputDataDirectory.resolve("config.xml").toUri().toURL()); Scenario scenario = ScenarioUtils.loadScenario(config); - Map>> buildingsPerZone = new HashMap<>(); - Map, Link>> regionLinksMap = GenerateSmallScaleCommercialTrafficDemand + Map>> facilitiesPerZone = new HashMap<>(); + Map, Link>> linksPerZone = GenerateSmallScaleCommercialTrafficDemand .filterLinksForZones(scenario, SmallScaleCommercialTrafficUtils.getIndexZones(shapeFileZonePath, config.global().getCoordinateSystem(), shapeFileZoneNameColumn), - buildingsPerZone); + facilitiesPerZone); - SmallScaleCommercialTrafficUtils.readExistingModels(scenario, sample, regionLinksMap); + SmallScaleCommercialTrafficUtils.readExistingModels(scenario, sample, linksPerZone); Assertions.assertEquals(2, CarriersUtils.getCarriers(scenario).getCarriers().size(), MatsimTestUtils.EPSILON); Assertions.assertEquals(1, CarriersUtils.getCarrierVehicleTypes(scenario).getVehicleTypes().size(), MatsimTestUtils.EPSILON); @@ -511,13 +514,13 @@ void testReducingDemandAfterAddingExistingScenarios_goods() throws Exception { Path output = Path.of(utils.getOutputDirectory()); assert(new File(output.resolve("calculatedData").toString()).mkdir()); Path inputDataDirectory = Path.of(utils.getPackageInputDirectory()); - String usedLanduseConfiguration = "useExistingDataDistribution"; + String usedLanduseConfiguration = "useOSMBuildingsAndLanduse"; String networkPath = "https://raw.githubusercontent.com/matsim-org/matsim-libs/master/examples/scenarios/freight-chessboard-9x9/grid9x9.xml"; String usedTrafficType = "goodsTraffic"; double sample = 1.; String shapeFileZoneNameColumn = "name"; Path pathToInvestigationAreaData = Path.of(utils.getPackageInputDirectory()).resolve("investigationAreaData.csv"); - Path pathToExistingDataDistributionToZones = Path.of(utils.getPackageInputDirectory()).resolve("dataDistributionPerZone.csv"); + createDefaultDataConnectionForOSM(landuseCategoriesAndDataConnection); ArrayList modesORvehTypes = new ArrayList<>( Arrays.asList("vehTyp1", "vehTyp2", "vehTyp3", "vehTyp4", "vehTyp5")); @@ -528,24 +531,25 @@ void testReducingDemandAfterAddingExistingScenarios_goods() throws Exception { config.setContext(inputDataDirectory.resolve("config.xml").toUri().toURL()); Scenario scenario = ScenarioUtils.loadScenario(config); TrafficVolumeGeneration.setInputParameters(usedTrafficType); + Map>> facilitiesPerZone = new HashMap<>(); Map> resultingDataPerZone = LanduseBuildingAnalysis .createInputDataDistribution(output, landuseCategoriesAndDataConnection, usedLanduseConfiguration, SCTUtils.getIndexLanduse(inputDataDirectory), SCTUtils.getZoneIndex(inputDataDirectory), SCTUtils.getIndexBuildings(inputDataDirectory), - SCTUtils.getIndexRegions(inputDataDirectory), shapeFileZoneNameColumn, buildingsPerZone, pathToInvestigationAreaData, pathToExistingDataDistributionToZones); + SCTUtils.getIndexRegions(inputDataDirectory), shapeFileZoneNameColumn, buildingsPerZone, pathToInvestigationAreaData); Map> trafficVolumePerTypeAndZone_start = TrafficVolumeGeneration .createTrafficVolume_start(resultingDataPerZone, output, sample, modesORvehTypes, usedTrafficType); Map> trafficVolumePerTypeAndZone_stop = TrafficVolumeGeneration .createTrafficVolume_stop(resultingDataPerZone, output, sample, modesORvehTypes, usedTrafficType); - Map, Link>> regionLinksMap = GenerateSmallScaleCommercialTrafficDemand - .filterLinksForZones(scenario, SCTUtils.getZoneIndex(inputDataDirectory), buildingsPerZone); + Map, Link>> linksPerZone = GenerateSmallScaleCommercialTrafficDemand + .filterLinksForZones(scenario, SCTUtils.getZoneIndex(inputDataDirectory), facilitiesPerZone); - SmallScaleCommercialTrafficUtils.readExistingModels(scenario, sample, regionLinksMap); + SmallScaleCommercialTrafficUtils.readExistingModels(scenario, sample, linksPerZone); - TrafficVolumeGeneration.reduceDemandBasedOnExistingCarriers(scenario, regionLinksMap, usedTrafficType, + TrafficVolumeGeneration.reduceDemandBasedOnExistingCarriers(scenario, linksPerZone, usedTrafficType, trafficVolumePerTypeAndZone_start, trafficVolumePerTypeAndZone_stop); // test for "area1" @@ -667,17 +671,17 @@ void testReducingDemandAfterAddingExistingScenarios_goods() throws Exception { void testReducingDemandAfterAddingExistingScenarios_commercialPersonTraffic() throws Exception { Map> landuseCategoriesAndDataConnection = new HashMap<>(); Map>> buildingsPerZone = new HashMap<>(); + Map>> facilitiesPerZone = new HashMap<>(); Path output = Path.of(utils.getOutputDirectory()); assert(new File(output.resolve("calculatedData").toString()).mkdir()); Path inputDataDirectory = Path.of(utils.getPackageInputDirectory()); - String usedLanduseConfiguration = "useExistingDataDistribution"; + String usedLanduseConfiguration = "useOSMBuildingsAndLanduse"; String networkPath = "https://raw.githubusercontent.com/matsim-org/matsim-libs/master/examples/scenarios/freight-chessboard-9x9/grid9x9.xml"; String usedTrafficType = "commercialPersonTraffic"; double sample = 1.; String shapeFileZoneNameColumn = "name"; Path pathToInvestigationAreaData = Path.of(utils.getPackageInputDirectory()).resolve("investigationAreaData.csv"); - Path pathToExistingDataDistributionToZones = Path.of(utils.getPackageInputDirectory()).resolve("dataDistributionPerZone.csv"); ArrayList modesORvehTypes = new ArrayList<>( List.of("total")); @@ -689,11 +693,12 @@ void testReducingDemandAfterAddingExistingScenarios_commercialPersonTraffic() th Scenario scenario = ScenarioUtils.loadScenario(config); TrafficVolumeGeneration.setInputParameters(usedTrafficType); + createDefaultDataConnectionForOSM(landuseCategoriesAndDataConnection); Map> resultingDataPerZone = LanduseBuildingAnalysis .createInputDataDistribution(output, landuseCategoriesAndDataConnection, usedLanduseConfiguration, SCTUtils.getIndexLanduse(inputDataDirectory), SCTUtils.getZoneIndex(inputDataDirectory), SCTUtils.getIndexBuildings(inputDataDirectory), - SCTUtils.getIndexRegions(inputDataDirectory), shapeFileZoneNameColumn, buildingsPerZone, pathToInvestigationAreaData, pathToExistingDataDistributionToZones); + SCTUtils.getIndexRegions(inputDataDirectory), shapeFileZoneNameColumn, buildingsPerZone, pathToInvestigationAreaData); Map> trafficVolumePerTypeAndZone_start = TrafficVolumeGeneration .createTrafficVolume_start(resultingDataPerZone, output, sample, modesORvehTypes, usedTrafficType); @@ -701,7 +706,7 @@ void testReducingDemandAfterAddingExistingScenarios_commercialPersonTraffic() th .createTrafficVolume_stop(resultingDataPerZone, output, sample, modesORvehTypes, usedTrafficType); Map, Link>> regionLinksMap = GenerateSmallScaleCommercialTrafficDemand - .filterLinksForZones(scenario, SCTUtils.getZoneIndex(inputDataDirectory), buildingsPerZone); + .filterLinksForZones(scenario, SCTUtils.getZoneIndex(inputDataDirectory), facilitiesPerZone); SmallScaleCommercialTrafficUtils.readExistingModels(scenario, sample, regionLinksMap); diff --git a/contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/TripDistributionMatrixTest.java b/contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/TripDistributionMatrixTest.java index 7190ba7350d..dd98d5e9ef7 100644 --- a/contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/TripDistributionMatrixTest.java +++ b/contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/TripDistributionMatrixTest.java @@ -28,6 +28,7 @@ import org.matsim.api.core.v01.network.Network; import org.matsim.core.network.NetworkUtils; import org.matsim.smallScaleCommercialTrafficGeneration.TrafficVolumeGeneration.TrafficVolumeKey; +import org.matsim.smallScaleCommercialTrafficGeneration.prepare.LanduseBuildingAnalysis; import org.matsim.testcases.MatsimTestUtils; import org.opengis.feature.simple.SimpleFeature; @@ -56,18 +57,17 @@ void testTripDistributionCommercialPersonTrafficTraffic() throws IOException { Path output = Path.of(utils.getOutputDirectory()); assert(new File(output.resolve("calculatedData").toString()).mkdir()); Path inputDataDirectory = Path.of(utils.getPackageInputDirectory()); - String usedLanduseConfiguration = "useExistingDataDistribution"; + String usedLanduseConfiguration = "useOSMBuildingsAndLanduse"; String networkLocation = "https://raw.githubusercontent.com/matsim-org/matsim-libs/master/examples/scenarios/freight-chessboard-9x9/grid9x9.xml"; Network network = NetworkUtils.readNetwork(networkLocation); String shapeFileZoneNameColumn = "name"; Path pathToInvestigationAreaData = Path.of(utils.getPackageInputDirectory()).resolve("investigationAreaData.csv"); - Path pathToExistingDataDistributionToZones = Path.of(utils.getPackageInputDirectory()).resolve("dataDistributionPerZone.csv"); Map> resultingDataPerZone = LanduseBuildingAnalysis .createInputDataDistribution(output, landuseCategoriesAndDataConnection, usedLanduseConfiguration, getIndexLanduse(inputDataDirectory), getZoneIndex(inputDataDirectory), getIndexBuildings(inputDataDirectory), - SCTUtils.getIndexRegions(inputDataDirectory), shapeFileZoneNameColumn, buildingsPerZone, pathToInvestigationAreaData, pathToExistingDataDistributionToZones); + SCTUtils.getIndexRegions(inputDataDirectory), shapeFileZoneNameColumn, buildingsPerZone, pathToInvestigationAreaData); String usedTrafficType = "commercialPersonTraffic"; double sample = 1.; @@ -148,17 +148,17 @@ void testTripDistributionGoodsTraffic() throws IOException { Path output = Path.of(utils.getOutputDirectory()); assert(new File(output.resolve("calculatedData").toString()).mkdir()); Path inputDataDirectory = Path.of(utils.getPackageInputDirectory()); - String usedLanduseConfiguration = "useExistingDataDistribution"; + String usedLanduseConfiguration = "useOSMBuildingsAndLanduse"; String networkLocation = "https://raw.githubusercontent.com/matsim-org/matsim-libs/master/examples/scenarios/freight-chessboard-9x9/grid9x9.xml"; Network network = NetworkUtils.readNetwork(networkLocation); String shapeFileZoneNameColumn = "name"; Path pathToInvestigationAreaData = Path.of(utils.getPackageInputDirectory()).resolve("investigationAreaData.csv"); - Path pathToExistingDataDistributionToZones = Path.of(utils.getPackageInputDirectory()).resolve("dataDistributionPerZone.csv"); + Map> resultingDataPerZone = LanduseBuildingAnalysis .createInputDataDistribution(output, landuseCategoriesAndDataConnection, usedLanduseConfiguration, getIndexLanduse(inputDataDirectory), getZoneIndex(inputDataDirectory), getIndexBuildings(inputDataDirectory), - SCTUtils.getIndexRegions(inputDataDirectory), shapeFileZoneNameColumn, buildingsPerZone, pathToInvestigationAreaData, pathToExistingDataDistributionToZones); + SCTUtils.getIndexRegions(inputDataDirectory), shapeFileZoneNameColumn, buildingsPerZone, pathToInvestigationAreaData); String usedTrafficType = "goodsTraffic"; double sample = 1.; diff --git a/contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/prepare/CreateDataDistributionOfStructureDataTest.java b/contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/prepare/CreateDataDistributionOfStructureDataTest.java new file mode 100644 index 00000000000..f7c499a5add --- /dev/null +++ b/contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/prepare/CreateDataDistributionOfStructureDataTest.java @@ -0,0 +1,50 @@ +package org.matsim.smallScaleCommercialTrafficGeneration.prepare; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.matsim.testcases.MatsimTestUtils; + +import java.nio.file.Path; + +class CreateDataDistributionOfStructureDataTest { + @RegisterExtension + private MatsimTestUtils utils = new MatsimTestUtils(); + + @Test + void testDataDistributionOfStructureData() { + String useOSMBuildingsAndLanduse = "useOSMBuildingsAndLanduse"; + String regionsShapeFileName = Path.of(utils.getPackageInputDirectory()).getParent().resolve("shp/testRegions.shp").toString(); + String regionsShapeRegionColumn = "region"; + String zoneShapeFileName = Path.of(utils.getPackageInputDirectory()).getParent().resolve("shp/testZones.shp").toString(); + String zoneShapeFileNameColumn = "name"; + String buildingsShapeFileName = Path.of(utils.getPackageInputDirectory()).getParent().resolve("shp/testBuildings.shp").toString(); + String shapeFileBuildingTypeColumn = "type"; + String landuseShapeFileName = Path.of(utils.getPackageInputDirectory()).getParent().resolve("shp/testLanduse.shp").toString(); + String shapeFileLanduseTypeColumn = "fclass"; + String shapeCRS = "EPSG:4326"; + String investigationAreaData = Path.of(utils.getPackageInputDirectory()).getParent().resolve("investigationAreaData.csv").toString(); + + new CreateDataDistributionOfStructureData().execute( + "--pathOutput", utils.getOutputDirectory(), + "--landuseConfiguration", useOSMBuildingsAndLanduse, + "--regionsShapeFileName", regionsShapeFileName, + "--regionsShapeRegionColumn", regionsShapeRegionColumn, + "--zoneShapeFileName", zoneShapeFileName, + "--zoneShapeFileNameColumn", zoneShapeFileNameColumn, + "--buildingsShapeFileName", buildingsShapeFileName, + "--shapeFileBuildingTypeColumn", shapeFileBuildingTypeColumn, + "--landuseShapeFileName", landuseShapeFileName, + "--shapeFileLanduseTypeColumn", shapeFileLanduseTypeColumn, + "--shapeCRS", shapeCRS, + "--pathToInvestigationAreaData", investigationAreaData); + + Assertions.assertTrue(Path.of(utils.getOutputDirectory()).resolve("dataDistributionPerZone.csv").toFile().exists()); + Assertions.assertTrue(Path.of(utils.getOutputDirectory()).resolve("dataDistributionPerZone.csv").toFile().length() > 0); + + Assertions.assertTrue(Path.of(utils.getOutputDirectory()).resolve("commercialFacilities.xml.gz").toFile().exists()); + Assertions.assertTrue(Path.of(utils.getOutputDirectory()).resolve("commercialFacilities.xml.gz").toFile().length() > 0); + + //TODO add tests for the created facilities + } +} diff --git a/contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/LanduseBuildingAnalysisTest.java b/contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/prepare/LanduseBuildingAnalysisTest.java similarity index 92% rename from contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/LanduseBuildingAnalysisTest.java rename to contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/prepare/LanduseBuildingAnalysisTest.java index fa640744c92..a65d97b0686 100644 --- a/contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/LanduseBuildingAnalysisTest.java +++ b/contribs/small-scale-traffic-generation/src/test/java/org/matsim/smallScaleCommercialTrafficGeneration/prepare/LanduseBuildingAnalysisTest.java @@ -17,12 +17,13 @@ * See also COPYING, LICENSE and WARRANTY file * * * * *********************************************************************** */ -package org.matsim.smallScaleCommercialTrafficGeneration; +package org.matsim.smallScaleCommercialTrafficGeneration.prepare; import it.unimi.dsi.fastutil.objects.Object2DoubleMap; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.matsim.smallScaleCommercialTrafficGeneration.SCTUtils; import org.matsim.testcases.MatsimTestUtils; import org.opengis.feature.simple.SimpleFeature; @@ -33,6 +34,8 @@ import java.util.List; import java.util.Map; +import static org.matsim.smallScaleCommercialTrafficGeneration.prepare.LanduseBuildingAnalysis.createDefaultDataConnectionForOSM; + /** * @author Ricardo Ewert * @@ -49,18 +52,19 @@ void testReadOfDataDistributionPerZoneAndBuildingAnalysis() throws IOException { Path output = Path.of(utils.getOutputDirectory()); assert(new File(output.resolve("calculatedData").toString()).mkdir()); - Path inputDataDirectory = Path.of(utils.getPackageInputDirectory()); - String usedLanduseConfiguration = "useExistingDataDistribution"; + Path inputDataDirectory = Path.of(utils.getPackageInputDirectory()).getParent(); + String usedLanduseConfiguration = "useOSMBuildingsAndLanduse"; String shapeFileZoneNameColumn = "name"; - Path pathToInvestigationAreaData = Path.of(utils.getPackageInputDirectory()).resolve("investigationAreaData.csv"); - Path pathToExistingDataDistributionToZones = Path.of(utils.getPackageInputDirectory()).resolve("dataDistributionPerZone.csv"); + Path pathToInvestigationAreaData = Path.of(utils.getPackageInputDirectory()).getParent().resolve("investigationAreaData.csv"); // Test if the reading of the existing data distribution works correctly + createDefaultDataConnectionForOSM(landuseCategoriesAndDataConnection); + Map> resultingDataPerZone = LanduseBuildingAnalysis .createInputDataDistribution(output, landuseCategoriesAndDataConnection, usedLanduseConfiguration, SCTUtils.getIndexLanduse(inputDataDirectory), SCTUtils.getZoneIndex(inputDataDirectory), SCTUtils.getIndexBuildings(inputDataDirectory), - SCTUtils.getIndexRegions(inputDataDirectory), shapeFileZoneNameColumn, buildingsPerZone, pathToInvestigationAreaData, pathToExistingDataDistributionToZones); + SCTUtils.getIndexRegions(inputDataDirectory), shapeFileZoneNameColumn, buildingsPerZone, pathToInvestigationAreaData); Assertions.assertEquals(3, resultingDataPerZone.size(), MatsimTestUtils.EPSILON); @@ -153,8 +157,6 @@ void testReadOfDataDistributionPerZoneAndBuildingAnalysis() throws IOException { // tests if the reading of the buildings works correctly List buildingsFeatures = SCTUtils.getIndexBuildings(inputDataDirectory).getAllFeatures(); Assertions.assertEquals(31, buildingsFeatures.size(), MatsimTestUtils.EPSILON); - LanduseBuildingAnalysis.analyzeBuildingType(buildingsFeatures, buildingsPerZone, - landuseCategoriesAndDataConnection, SCTUtils.getIndexLanduse(inputDataDirectory), SCTUtils.getZoneIndex(inputDataDirectory)); Assertions.assertEquals(3, buildingsPerZone.size(), MatsimTestUtils.EPSILON); Assertions.assertTrue(buildingsPerZone.containsKey("area1")); @@ -162,9 +164,9 @@ void testReadOfDataDistributionPerZoneAndBuildingAnalysis() throws IOException { Assertions.assertTrue(buildingsPerZone.containsKey("area3")); // test for area1 - Map> builingsPerArea1 = buildingsPerZone.get("area1"); - Assertions.assertEquals(7, builingsPerArea1.size(), MatsimTestUtils.EPSILON); - List inhabitantsBuildings = builingsPerArea1.get("Inhabitants"); + Map> buildingsPerArea1 = buildingsPerZone.get("area1"); + Assertions.assertEquals(7, buildingsPerArea1.size(), MatsimTestUtils.EPSILON); + List inhabitantsBuildings = buildingsPerArea1.get("Inhabitants"); Assertions.assertEquals(4, inhabitantsBuildings.size(), MatsimTestUtils.EPSILON); for (SimpleFeature singleBuilding : inhabitantsBuildings) { int id = (int) (long) singleBuilding.getAttribute("osm_id"); @@ -183,13 +185,13 @@ void testReadOfDataDistributionPerZoneAndBuildingAnalysis() throws IOException { } else Assertions.fail(); } - Assertions.assertFalse(builingsPerArea1.containsKey("Employee Primary Sector")); - Assertions.assertEquals(1, builingsPerArea1.get("Employee Construction").size(), MatsimTestUtils.EPSILON); - Assertions.assertEquals(1, builingsPerArea1.get("Employee Secondary Sector Rest").size(), MatsimTestUtils.EPSILON); - Assertions.assertEquals(2, builingsPerArea1.get("Employee Retail").size(), MatsimTestUtils.EPSILON); - Assertions.assertEquals(1, builingsPerArea1.get("Employee Traffic/Parcels").size(), MatsimTestUtils.EPSILON); - Assertions.assertEquals(2, builingsPerArea1.get("Employee Tertiary Sector Rest").size(), MatsimTestUtils.EPSILON); - Assertions.assertEquals(6, builingsPerArea1.get("Employee").size(), MatsimTestUtils.EPSILON); + Assertions.assertFalse(buildingsPerArea1.containsKey("Employee Primary Sector")); + Assertions.assertEquals(1, buildingsPerArea1.get("Employee Construction").size(), MatsimTestUtils.EPSILON); + Assertions.assertEquals(1, buildingsPerArea1.get("Employee Secondary Sector Rest").size(), MatsimTestUtils.EPSILON); + Assertions.assertEquals(2, buildingsPerArea1.get("Employee Retail").size(), MatsimTestUtils.EPSILON); + Assertions.assertEquals(1, buildingsPerArea1.get("Employee Traffic/Parcels").size(), MatsimTestUtils.EPSILON); + Assertions.assertEquals(2, buildingsPerArea1.get("Employee Tertiary Sector Rest").size(), MatsimTestUtils.EPSILON); + Assertions.assertEquals(6, buildingsPerArea1.get("Employee").size(), MatsimTestUtils.EPSILON); // test for area2 Map> builingsPerArea2 = buildingsPerZone.get("area2"); @@ -244,16 +246,18 @@ void testLanduseDistribution() throws IOException { Path output = Path.of(utils.getOutputDirectory()); assert(new File(output.resolve("calculatedData").toString()).mkdir()); - Path inputDataDirectory = Path.of(utils.getPackageInputDirectory()); + Path inputDataDirectory = Path.of(utils.getPackageInputDirectory()).getParent(); String usedLanduseConfiguration = "useOSMBuildingsAndLanduse"; String shapeFileZoneNameColumn = "name"; - Path pathToInvestigationAreaData = Path.of(utils.getPackageInputDirectory()).resolve("investigationAreaData.csv"); + Path pathToInvestigationAreaData = Path.of(utils.getPackageInputDirectory()).getParent().resolve("investigationAreaData.csv"); + createDefaultDataConnectionForOSM(landuseCategoriesAndDataConnection); + // Analyze resultingData per zone Map> resultingDataPerZone = LanduseBuildingAnalysis .createInputDataDistribution(output, landuseCategoriesAndDataConnection, usedLanduseConfiguration, SCTUtils.getIndexLanduse(inputDataDirectory), SCTUtils.getZoneIndex(inputDataDirectory), SCTUtils.getIndexBuildings(inputDataDirectory), - SCTUtils.getIndexRegions(inputDataDirectory), shapeFileZoneNameColumn, buildingsPerZone, pathToInvestigationAreaData, null); + SCTUtils.getIndexRegions(inputDataDirectory), shapeFileZoneNameColumn, buildingsPerZone, pathToInvestigationAreaData); Assertions.assertEquals(3, resultingDataPerZone.size(), MatsimTestUtils.EPSILON); diff --git a/contribs/small-scale-traffic-generation/test/input/org/matsim/smallScaleCommercialTrafficGeneration/commercialFacilities.xml.gz b/contribs/small-scale-traffic-generation/test/input/org/matsim/smallScaleCommercialTrafficGeneration/commercialFacilities.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..1c611cba984d48279f6aa3bc371326cbfeed55ec GIT binary patch literal 1378 zcmV-o1)cgIiwFP!00000|Ls~$Z`(!?J;%R7<-Mz!*`1FqYzJtP0yz{7YWI*{0!Ee{ z7BUq`v=jH&cc_(NBal6$YqA({?gH zYubyW>bKLMzPIY>@xkGD&wqJ#`uf$2(Rn>-W=+>jm!p%{C#Nrd9#!wU?)?wr@rMr| z!e!kpo69g?T#V0~*>pKR>(0h^x4gNg;8&{SgM-7ntH%dVZbvtx=Ip5IrpxZft7g`} z#4=zt`gl~aK`EiQ{;&3;I`+rbpDuE5xWf517&m_h zI;>Eq1QKFQNB}WqP6c%z2L%>H$fl77L;ZBR=$d+QGdh`0y7|IkTXsXm;4|RAY|oq4 z*WRiKl)O(OF=zzLNs%$`lE6lof;uh#>(+G>iaZdk8l{7z>1&H9!Ujk2EHYD+t*<3$qxq3Lrng1K&J^ zXf-nha5c8^*D4CfKMy1c@?Xp{3`Ru&iF$x_Fd0rteC>($cQ4|v6 zErohK$cZ#X%Al}$|L=x$;H^Sk3hGfMWHeDS@}EK(lEG0|Qe=*Z=|2%{58UAzW8R)U zlG@n4uxSaK5r@PSou-({3Qe&HxZ+bbG*^Oq#rG%?uvG*!at`XoM%?VUt>IEA*f)-= zl&qLN067j9fkl0O-b}`?>cwO_`{EkCtKjDq_=c2RdRvyr?7Fak?+PcoR?bLSpE;&uKStIMAZZXzWK;bx)p6(E#tGJRBRw?)#BBluotubqX1 zg_VN)J^^qwt~y-l+;i|<0E+9*$%~+SMVBf0uJ1u=7J9p#QuI05%)wtFhasw04NDpN zLhXYgAdN#oT6m$yj@{v)I~|I-Z*MrHp4=QFn7SJGx7W>$jDM5VuNKYaT~6;mN;)I;*kf;jlBzTI-g^#+d~yB4Zg&JINA#puvC?}&j|cbq+B4pL z>0rP=s-@Yr2o(2E=*bV_zD7))c49d>AW;q)Ga+j>PygYve@BlF2E)Dhf9$)i?pKhb z*G~ecmlf&wh2Zj9FdCC@jlGc>7=B&Dk1?1ePFXzH3FMaZA#j8Y1}HJ%fbj3WiX1Ek z#mQr~985)`x#c0ILOos%ddXus3j-0PZIO|Q$s(YcXr{!UqVaVhE&kjcxdu=di>_Lf zsi;WuBzNhaDWcDzZVjRgIl1ie;7dXuY!jkI8afSQ5b4hzj1W9gOT$ksd3jV2Lxr@{ z-ErF{hacAm>&6hWfVjo?@+j-}I0{)Xw%i{FL3$vOzaiL%O7^10$^B1=2D$&D!A!Xj zvJzo%o1Y{j^iz91{s@`BmgAf$h_ko~_559|XzF|3q!A#wL+uOY-ii(ee9_|uF(7FI zK+pGB)VTIv@|Z$obRNoCH+QxlRSy0y)%;Mg)3xexf&$+e=mo+B#d_EHm;<=QT7CWY z5O3}3vBXdiuC;jIb6NGy4T*)<$I(6tp7pNK)HenUm;O85ejEz=qS+BZLSJ1mMK8Gu kxaR!WpJ(*vK^*&D^B>O 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

J75JCPVFInA942^Yb>e#~R>1LC6!URk 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 DflCXj literal 31240 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