Skip to content

Commit

Permalink
make zones attributable (matsim-org#3245)
Browse files Browse the repository at this point in the history
simplify / deduplicate code
  • Loading branch information
nkuehnel committed May 2, 2024
1 parent 2bd106f commit 6a2b062
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@
import org.matsim.api.core.v01.BasicLocation;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Identifiable;
import org.matsim.api.core.v01.network.Link;
import org.matsim.utils.objectattributes.attributable.Attributable;

import javax.annotation.Nullable;
import java.util.List;

public interface Zone extends BasicLocation, Identifiable<Zone> {
public interface Zone extends BasicLocation, Identifiable<Zone>, Attributable {
@Nullable
PreparedPolygon getPreparedGeometry();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
import org.locationtech.jts.geom.prep.PreparedPolygon;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
import org.matsim.core.utils.geometry.geotools.MGC;
import org.matsim.utils.objectattributes.attributable.Attributes;
import org.matsim.utils.objectattributes.attributable.AttributesImpl;

import javax.annotation.Nullable;
import java.util.List;

public class ZoneImpl implements Zone {

Expand All @@ -17,6 +17,9 @@ public class ZoneImpl implements Zone {
private final Coord centroid;
private String type;

private final Attributes attributes = new AttributesImpl();


public ZoneImpl(Id<Zone> id, PreparedPolygon preparedGeometry, @Nullable String type) {
this(id, preparedGeometry, MGC.point2Coord(preparedGeometry.getGeometry().getCentroid()), type);
}
Expand Down Expand Up @@ -67,4 +70,8 @@ public static ZoneImpl createDummyZone(Id<Zone> id, Coord centroid) {
return new ZoneImpl(id, null, centroid, null);
}

@Override
public Attributes getAttributes() {
return attributes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import one.util.streamex.StreamEx;
import org.apache.commons.lang3.tuple.Pair;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygonal;
import org.locationtech.jts.geom.prep.PreparedGeometry;
import org.locationtech.jts.geom.prep.PreparedPolygon;
import org.matsim.api.core.v01.Id;
Expand All @@ -26,6 +27,9 @@
import org.matsim.contrib.common.zones.util.ZoneFinderImpl;
import org.matsim.core.config.ConfigGroup;
import org.matsim.core.utils.geometry.geotools.MGC;
import org.matsim.core.utils.gis.GeoFileReader;
import org.opengis.feature.Property;
import org.opengis.feature.simple.SimpleFeature;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
Expand All @@ -39,14 +43,15 @@
import java.util.stream.Collectors;

import static java.util.stream.Collectors.*;
import static org.matsim.utils.gis.shp2matsim.ShpGeometryUtils.loadPreparedPolygons;

/**
* @author nkuehnel / MOIA
*/
public final class ZoneSystemUtils {


public static final String THE_GEOM = "the_geom";

private ZoneSystemUtils() {}

public static ZoneSystem createZoneSystem(Network network, ZoneSystemParams zoneSystemParams) {
Expand All @@ -65,11 +70,9 @@ public static ZoneSystem createZoneSystem(@Nullable URL context, @Nonnull Networ
case GISFileZoneSystemParams.SET_NAME -> {
Preconditions.checkNotNull(((GISFileZoneSystemParams) zoneSystemParams).zonesShapeFile);
Preconditions.checkNotNull(context);

final List<PreparedPolygon> preparedGeometries = loadPreparedPolygons(
ConfigGroup.getInputFileURL(context, ((GISFileZoneSystemParams) zoneSystemParams).zonesShapeFile));
yield ZoneSystemUtils.createFromPreparedGeometries(network,
EntryStream.of(preparedGeometries).mapKeys(i -> (i + 1) + "").toMap());
URL url = ConfigGroup.getInputFileURL(context, ((GISFileZoneSystemParams) zoneSystemParams).zonesShapeFile);
Collection<SimpleFeature> features = GeoFileReader.getAllFeatures(url);
yield ZoneSystemUtils.createFromFeatures(network, features);
}
case SquareGridZoneSystemParams.SET_NAME -> {
Preconditions.checkNotNull(((SquareGridZoneSystemParams) zoneSystemParams).cellSize);
Expand All @@ -90,35 +93,52 @@ public static ZoneSystem createZoneSystem(@Nullable URL context, @Nonnull Networ
return zoneSystem;
}

public static ZoneSystem createFromPreparedGeometries(Network network, Map<String, PreparedPolygon> geometries) {
public static ZoneSystem createFromFeatures(Network network, Collection<SimpleFeature> features) {

Map<String, PreparedFeature> featureById = StreamEx.of(features.stream())
.mapToEntry(SimpleFeature::getID, sf -> new PreparedFeature(sf, new PreparedPolygon((Polygonal) sf.getDefaultGeometry())))
.toMap();

//geometries without links are skipped
Map<String, List<Link>> linksByGeometryId = StreamEx.of(network.getLinks().values())
.mapToEntry(l -> getGeometryIdForLink(l, geometries), l -> l)
.mapToEntry(l -> getGeometryIdForLink(l, featureById), l -> l)
.filterKeys(Objects::nonNull)
.grouping(toList());

//the zonal system contains only zones that have at least one link
Map<Id<Zone>, Zone> zones = EntryStream.of(linksByGeometryId)
.mapKeyValue((id, links) -> new ZoneImpl(Id.create(id, Zone.class), geometries.get(id), null))
.mapKeyValue((id, links) -> {
PreparedFeature preparedFeature = featureById.get(id);
ZoneImpl zone = new ZoneImpl(Id.create(id, Zone.class), preparedFeature.preparedPolygon, null);
for (Property attribute : preparedFeature.sf.getProperties()) {
String attributeName = attribute.getName().toString();
Object att = preparedFeature.sf().getAttribute(attributeName);
if(!attributeName.equals(THE_GEOM) && att != null) {
zone.getAttributes().putAttribute(attributeName, att);
}
}
return zone;
})
.collect(IdCollectors.toIdMap(Zone.class, Identifiable::getId, zone -> zone));


return new ZoneSystemImpl(zones.values(), new ZoneFinderImpl(zones), network);
}

private record PreparedFeature(SimpleFeature sf, PreparedPolygon preparedPolygon){}

/**
* @param link
* @return the the {@code PreparedGeometry} that contains the {@code linkId}.
* If a given link's {@code Coord} borders two or more cells, the allocation to a cell is random.
* Result may be null in case the given link is outside of the service area.
*/
@Nullable
private static String getGeometryIdForLink(Link link, Map<String, PreparedPolygon> geometries) {
private static String getGeometryIdForLink(Link link, Map<String, PreparedFeature> features) {
Point linkCoord = MGC.coord2Point(link.getToNode().getCoord());
return geometries.entrySet()
return features.entrySet()
.stream()
.filter(e -> e.getValue().intersects(linkCoord))
.filter(e -> e.getValue().preparedPolygon.intersects(linkCoord))
.findAny()
.map(Map.Entry::getKey)
.orElse(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,32 +20,15 @@

package org.matsim.contrib.drt.analysis.zonal;

import com.google.common.base.Preconditions;
import one.util.streamex.EntryStream;
import org.locationtech.jts.geom.prep.PreparedGeometry;
import org.locationtech.jts.geom.prep.PreparedPolygon;
import org.matsim.api.core.v01.network.Network;
import org.matsim.contrib.common.zones.Zone;
import org.matsim.contrib.common.zones.ZoneSystem;
import org.matsim.contrib.common.zones.ZoneSystemParams;
import org.matsim.contrib.common.zones.ZoneSystemUtils;
import org.matsim.contrib.common.zones.systems.grid.GISFileZoneSystemParams;
import org.matsim.contrib.common.zones.systems.grid.h3.H3GridZoneSystemParams;
import org.matsim.contrib.common.zones.systems.grid.h3.H3ZoneSystem;
import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystem;
import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystemParams;
import org.matsim.contrib.drt.analysis.DrtEventSequenceCollector;
import org.matsim.contrib.drt.run.DrtConfigGroup;
import org.matsim.contrib.dvrp.run.AbstractDvrpModeModule;
import org.matsim.core.config.ConfigGroup;
import org.matsim.core.controler.MatsimServices;

import java.util.List;
import java.util.function.Predicate;

import static org.matsim.utils.gis.shp2matsim.ShpGeometryUtils.loadPreparedGeometries;
import static org.matsim.utils.gis.shp2matsim.ShpGeometryUtils.loadPreparedPolygons;

/**
* @author Michal Maciejewski (michalm)
*/
Expand All @@ -63,53 +46,11 @@ public void install() {
if (drtCfg.getZonalSystemParams().isPresent()) {
DrtZoneSystemParams params = drtCfg.getZonalSystemParams().get();
ZoneSystemParams zoneSystemParams = params.getZoneSystemParams();
String crs = getConfig().global().getCoordinateSystem();

bindModal(ZoneSystem.class).toProvider(modalProvider(getter -> {
Network network = getter.getModal(Network.class);
switch (zoneSystemParams.getName()) {
case GISFileZoneSystemParams.SET_NAME: {
Preconditions.checkNotNull(((GISFileZoneSystemParams) zoneSystemParams).zonesShapeFile);
final List<PreparedPolygon> preparedGeometries = loadPreparedPolygons(
ConfigGroup.getInputFileURL(getConfig().getContext(), ((GISFileZoneSystemParams) zoneSystemParams).zonesShapeFile));
return ZoneSystemUtils.createFromPreparedGeometries(network,
EntryStream.of(preparedGeometries).mapKeys(i -> (i + 1) + "").toMap());
}

case SquareGridZoneSystemParams.SET_NAME: {
Preconditions.checkNotNull(((SquareGridZoneSystemParams) zoneSystemParams).cellSize);
Predicate<Zone> zoneFilter;
if(drtCfg.operationalScheme == DrtConfigGroup.OperationalScheme.serviceAreaBased) {
List<PreparedGeometry> serviceAreas = loadPreparedGeometries(ConfigGroup.getInputFileURL(getConfig().getContext(),
drtCfg.drtServiceAreaShapeFile));
zoneFilter = zone -> serviceAreas.stream().anyMatch(serviceArea -> serviceArea.intersects(zone.getPreparedGeometry().getGeometry()));
} else {
zoneFilter = zone -> true;
}

SquareGridZoneSystem squareGridZoneSystem = new SquareGridZoneSystem(network, ((SquareGridZoneSystemParams) zoneSystemParams).cellSize, zoneFilter);
return squareGridZoneSystem;
}

case H3GridZoneSystemParams.SET_NAME: {

Preconditions.checkNotNull(((H3GridZoneSystemParams) zoneSystemParams).h3Resolution);
String crs = getConfig().global().getCoordinateSystem();

Predicate<Zone> zoneFilter;
if (drtCfg.operationalScheme == DrtConfigGroup.OperationalScheme.serviceAreaBased) {
List<PreparedGeometry> serviceAreas = loadPreparedGeometries(ConfigGroup.getInputFileURL(getConfig().getContext(),
drtCfg.drtServiceAreaShapeFile));
zoneFilter = zone -> serviceAreas.stream().anyMatch(serviceArea -> serviceArea.intersects(zone.getPreparedGeometry().getGeometry()));
} else {
zoneFilter = zone -> true;
}

return new H3ZoneSystem(crs, ((H3GridZoneSystemParams) zoneSystemParams).h3Resolution, network, zoneFilter);
}

default:
throw new RuntimeException("Unsupported zone generation");
}
return ZoneSystemUtils.createZoneSystem(getConfig().getContext(), network, zoneSystemParams, crs);
})).asEagerSingleton();

bindModal(DrtZoneTargetLinkSelector.class).toProvider(modalProvider(getter -> {
Expand Down

0 comments on commit 6a2b062

Please sign in to comment.