diff --git a/contribs/av/src/main/java/org/matsim/contrib/av/robotaxi/run/RunDrtAndTaxiExample.java b/contribs/av/src/main/java/org/matsim/contrib/av/robotaxi/run/RunDrtAndTaxiExample.java
index 5c95006afa8..d644f7d9a92 100644
--- a/contribs/av/src/main/java/org/matsim/contrib/av/robotaxi/run/RunDrtAndTaxiExample.java
+++ b/contribs/av/src/main/java/org/matsim/contrib/av/robotaxi/run/RunDrtAndTaxiExample.java
@@ -23,6 +23,7 @@
 import java.net.URL;
 
 import org.matsim.api.core.v01.Scenario;
+import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystemParams;
 import org.matsim.contrib.drt.run.DrtControlerCreator;
 import org.matsim.contrib.drt.run.MultiModeDrtConfigGroup;
 import org.matsim.contrib.drt.run.MultiModeDrtModule;
@@ -33,6 +34,7 @@
 import org.matsim.contrib.taxi.run.MultiModeTaxiConfigGroup;
 import org.matsim.contrib.taxi.run.MultiModeTaxiModule;
 import org.matsim.core.config.Config;
+import org.matsim.core.config.ConfigGroup;
 import org.matsim.core.config.ConfigUtils;
 import org.matsim.core.controler.Controler;
 import org.matsim.core.controler.OutputDirectoryHierarchy;
@@ -44,8 +46,13 @@
  */
 public class RunDrtAndTaxiExample {
 	public static void run(URL configUrl, boolean otfvis) {
+		DvrpConfigGroup dvrpConfigGroup = new DvrpConfigGroup();
+		ConfigGroup zoneParams = dvrpConfigGroup.getTravelTimeMatrixParams().createParameterSet(SquareGridZoneSystemParams.SET_NAME);
+		dvrpConfigGroup.getTravelTimeMatrixParams().addParameterSet(zoneParams);
+
+
 		Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), new MultiModeTaxiConfigGroup(),
-				new DvrpConfigGroup(), new OTFVisConfigGroup());
+			dvrpConfigGroup, new OTFVisConfigGroup());
 		Scenario scenario = DrtControlerCreator.createScenarioWithDrtRouteFactory(config);
 		ScenarioUtils.loadScenario(scenario);
 		config.controller()
diff --git a/contribs/commercialTrafficApplications/src/main/java/org/matsim/contrib/commercialTrafficApplications/jointDemand/examples/RunJointDemandDRTExample.java b/contribs/commercialTrafficApplications/src/main/java/org/matsim/contrib/commercialTrafficApplications/jointDemand/examples/RunJointDemandDRTExample.java
index 68addb0a68a..72144a8d440 100644
--- a/contribs/commercialTrafficApplications/src/main/java/org/matsim/contrib/commercialTrafficApplications/jointDemand/examples/RunJointDemandDRTExample.java
+++ b/contribs/commercialTrafficApplications/src/main/java/org/matsim/contrib/commercialTrafficApplications/jointDemand/examples/RunJointDemandDRTExample.java
@@ -29,6 +29,7 @@
 import org.matsim.contrib.commercialTrafficApplications.jointDemand.ChangeCommercialJobOperator;
 import org.matsim.contrib.commercialTrafficApplications.jointDemand.JointDemandConfigGroup;
 import org.matsim.contrib.commercialTrafficApplications.jointDemand.JointDemandModule;
+import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystemParams;
 import org.matsim.contrib.drt.optimizer.insertion.extensive.ExtensiveInsertionSearchParams;
 import org.matsim.contrib.drt.run.DrtConfigGroup;
 import org.matsim.contrib.drt.run.DrtConfigs;
@@ -36,6 +37,7 @@
 import org.matsim.contrib.drt.run.MultiModeDrtConfigGroup;
 import org.matsim.contrib.dvrp.run.DvrpConfigGroup;
 import org.matsim.contrib.dvrp.run.DvrpQSimComponents;
+import org.matsim.core.config.ConfigGroup;
 import org.matsim.freight.carriers.FreightCarriersConfigGroup;
 import org.matsim.freight.carriers.CarriersUtils;
 import org.matsim.core.config.Config;
@@ -132,8 +134,11 @@ private static void prepareConfig(Config config) {
     }
 
     private static void loadConfigGroups(Config config) {
-        ConfigUtils.addOrGetModule(config, DvrpConfigGroup.class);
-        MultiModeDrtConfigGroup multiModeDrtConfigGroup = ConfigUtils.addOrGetModule(config, MultiModeDrtConfigGroup.class);
+		DvrpConfigGroup dvrpConfigGroup = ConfigUtils.addOrGetModule(config, DvrpConfigGroup.class);
+		ConfigGroup zoneParams = dvrpConfigGroup.getTravelTimeMatrixParams().createParameterSet(SquareGridZoneSystemParams.SET_NAME);
+		dvrpConfigGroup.getTravelTimeMatrixParams().addParameterSet(zoneParams);
+
+		MultiModeDrtConfigGroup multiModeDrtConfigGroup = ConfigUtils.addOrGetModule(config, MultiModeDrtConfigGroup.class);
 
         DrtConfigGroup drtCfg = new DrtConfigGroup();
         drtCfg.maxWaitTime = 2 * 3600;
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/util/ReflectiveConfigGroupWithConfigurableParameterSets.java b/contribs/common/src/main/java/org/matsim/contrib/common/util/ReflectiveConfigGroupWithConfigurableParameterSets.java
similarity index 99%
rename from contribs/dvrp/src/main/java/org/matsim/contrib/util/ReflectiveConfigGroupWithConfigurableParameterSets.java
rename to contribs/common/src/main/java/org/matsim/contrib/common/util/ReflectiveConfigGroupWithConfigurableParameterSets.java
index f732260b4e6..23a51809991 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/util/ReflectiveConfigGroupWithConfigurableParameterSets.java
+++ b/contribs/common/src/main/java/org/matsim/contrib/common/util/ReflectiveConfigGroupWithConfigurableParameterSets.java
@@ -18,19 +18,18 @@
  * *********************************************************************** *
  */
 
-package org.matsim.contrib.util;
+package org.matsim.contrib.common.util;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Verify;
+import org.matsim.core.config.ConfigGroup;
+import org.matsim.core.config.ReflectiveConfigGroup;
 
 import java.util.HashMap;
 import java.util.Map;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
-import org.matsim.core.config.ConfigGroup;
-import org.matsim.core.config.ReflectiveConfigGroup;
-
-import com.google.common.base.Preconditions;
-import com.google.common.base.Verify;
-
 /**
  * Provides additional functionality for handling parameter sets according to definitions.
  * <p>
diff --git a/contribs/common/src/main/java/org/matsim/contrib/common/zones/GridZoneSystem.java b/contribs/common/src/main/java/org/matsim/contrib/common/zones/GridZoneSystem.java
new file mode 100644
index 00000000000..65e75baaea9
--- /dev/null
+++ b/contribs/common/src/main/java/org/matsim/contrib/common/zones/GridZoneSystem.java
@@ -0,0 +1,11 @@
+package org.matsim.contrib.common.zones;
+
+import org.matsim.api.core.v01.Coord;
+
+import java.util.Optional;
+
+public interface GridZoneSystem extends ZoneSystem {
+
+	Optional<Zone> getZoneForCoord(Coord coord);
+
+}
diff --git a/contribs/common/src/main/java/org/matsim/contrib/common/zones/Zone.java b/contribs/common/src/main/java/org/matsim/contrib/common/zones/Zone.java
index dbf0c77caf8..4a820fb6dbf 100644
--- a/contribs/common/src/main/java/org/matsim/contrib/common/zones/Zone.java
+++ b/contribs/common/src/main/java/org/matsim/contrib/common/zones/Zone.java
@@ -1,6 +1,6 @@
 package org.matsim.contrib.common.zones;
 
-import org.locationtech.jts.geom.prep.PreparedGeometry;
+import org.locationtech.jts.geom.prep.PreparedPolygon;
 import org.matsim.api.core.v01.BasicLocation;
 import org.matsim.api.core.v01.Coord;
 import org.matsim.api.core.v01.Identifiable;
@@ -11,9 +11,9 @@
 
 public interface Zone extends BasicLocation, Identifiable<Zone> {
 	@Nullable
-	PreparedGeometry getPreparedGeometry();
+	PreparedPolygon getPreparedGeometry();
 
 	Coord getCentroid();
+	String getType();
 
-	List<Link> getLinks();
 }
diff --git a/contribs/common/src/main/java/org/matsim/contrib/common/zones/ZoneImpl.java b/contribs/common/src/main/java/org/matsim/contrib/common/zones/ZoneImpl.java
index 64b4a009733..9e327306915 100644
--- a/contribs/common/src/main/java/org/matsim/contrib/common/zones/ZoneImpl.java
+++ b/contribs/common/src/main/java/org/matsim/contrib/common/zones/ZoneImpl.java
@@ -1,6 +1,6 @@
 package org.matsim.contrib.common.zones;
 
-import org.locationtech.jts.geom.prep.PreparedGeometry;
+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;
@@ -13,20 +13,20 @@ public class ZoneImpl implements Zone {
 
 	private final Id<Zone> id;
 	@Nullable
-	private final PreparedGeometry preparedGeometry; //null for virtual/dummy zones
-	private final List<Link> links;
+	private PreparedPolygon preparedGeometry; //null for virtual/dummy zones
 	private final Coord centroid;
+	private String type;
 
-	public ZoneImpl(Id<Zone> id, PreparedGeometry preparedGeometry, List<Link> links) {
-		this(id, preparedGeometry, links, MGC.point2Coord(preparedGeometry.getGeometry().getCentroid()));
+	public ZoneImpl(Id<Zone> id, PreparedPolygon preparedGeometry, @Nullable String type) {
+		this(id, preparedGeometry, MGC.point2Coord(preparedGeometry.getGeometry().getCentroid()), type);
 	}
 
-	private ZoneImpl(Id<Zone> id, @Nullable PreparedGeometry preparedGeometry, List<Link> links, Coord centroid) {
+	public ZoneImpl(Id<Zone> id, @Nullable PreparedPolygon preparedGeometry, Coord centroid, @Nullable String type) {
 		this.id = id;
 		this.preparedGeometry = preparedGeometry;
-		this.links = links;
 		this.centroid = centroid;
-	}
+        this.type = type;
+    }
 
 	@Override
 	public Id<Zone> getId() {
@@ -40,7 +40,7 @@ public Coord getCoord() {
 
 	@Override
 	@Nullable
-	public PreparedGeometry getPreparedGeometry() {
+	public PreparedPolygon getPreparedGeometry() {
 		return preparedGeometry;
 	}
 
@@ -50,16 +50,21 @@ public Coord getCentroid() {
 	}
 
 	@Override
-	public List<Link> getLinks() {
-		return links;
+	public String getType() {
+		return type;
 	}
 
 	boolean isDummy() {
 		return preparedGeometry == null;
 	}
 
-	public static ZoneImpl createDummyZone(Id<Zone> id, List<Link> links, Coord centroid) {
-		return new ZoneImpl(id, null, links, centroid);
+	public void setGeometry(PreparedPolygon preparedPolygon) {
+		this.preparedGeometry = preparedPolygon;
+	}
+
+
+	public static ZoneImpl createDummyZone(Id<Zone> id, Coord centroid) {
+		return new ZoneImpl(id, null, centroid, null);
 	}
 
 }
diff --git a/contribs/common/src/main/java/org/matsim/contrib/common/zones/ZoneSystem.java b/contribs/common/src/main/java/org/matsim/contrib/common/zones/ZoneSystem.java
index 11299b67ad6..9bd5fb6b2b1 100644
--- a/contribs/common/src/main/java/org/matsim/contrib/common/zones/ZoneSystem.java
+++ b/contribs/common/src/main/java/org/matsim/contrib/common/zones/ZoneSystem.java
@@ -2,13 +2,19 @@
 
 import org.matsim.api.core.v01.Id;
 import org.matsim.api.core.v01.network.Link;
+import org.matsim.api.core.v01.network.Node;
 
-import javax.annotation.Nullable;
+import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 public interface ZoneSystem {
-	@Nullable
-	Zone getZoneForLinkId(Id<Link> linkId);
+	Optional<Zone> getZoneForLinkId(Id<Link> link);
+
+	Optional<Zone> getZoneForNodeId(Id<Node> nodeId);
+
+	List<Link> getLinksForZoneId(Id<Zone> zone);
+
 
 	Map<Id<Zone>, Zone> getZones();
 }
diff --git a/contribs/common/src/main/java/org/matsim/contrib/common/zones/ZoneSystemImpl.java b/contribs/common/src/main/java/org/matsim/contrib/common/zones/ZoneSystemImpl.java
index 958575ecf40..31b05b6130c 100644
--- a/contribs/common/src/main/java/org/matsim/contrib/common/zones/ZoneSystemImpl.java
+++ b/contribs/common/src/main/java/org/matsim/contrib/common/zones/ZoneSystemImpl.java
@@ -1,36 +1,61 @@
 package org.matsim.contrib.common.zones;
 
-import org.apache.commons.lang3.tuple.Pair;
 import org.matsim.api.core.v01.Id;
 import org.matsim.api.core.v01.IdMap;
 import org.matsim.api.core.v01.network.Link;
+import org.matsim.api.core.v01.network.Network;
+import org.matsim.api.core.v01.network.Node;
+import org.matsim.contrib.common.zones.util.ZoneFinder;
 
-import javax.annotation.Nullable;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Map;
+import java.util.*;
 
 public class ZoneSystemImpl implements ZoneSystem {
 
 	private final Map<Id<Zone>, Zone> zones = new IdMap<>(Zone.class);
 	private final IdMap<Link, Zone> link2zone = new IdMap<>(Link.class);
 
+	private final IdMap<Node, Zone> nodeToZoneMap = new IdMap<>(Node.class);
+
+	private final IdMap<Zone, List<Link>> zoneToLinksMap = new IdMap<>(Zone.class);
+
 	public ZoneSystemImpl(Collection<Zone> zones) {
 		zones.forEach(zone -> this.zones.put(zone.getId(), zone));
-		zones.stream()
-			.flatMap(zone -> zone.getLinks().stream().map(link -> Pair.of(link.getId(), zone)))
-			.forEach(idZonePair -> link2zone.put(idZonePair.getKey(), idZonePair.getValue()));
+	}
+
+	public ZoneSystemImpl(Collection<Zone> zones, ZoneFinder zoneFinder, Network network) {
+		zones.forEach(zone -> this.zones.put(zone.getId(), zone));
+
+		IdMap<Node, Zone> nodeToZoneMap = ZoneSystemUtils.createNodeToZoneMap(network, zoneFinder);
+		IdMap<Link, Zone> linkToZoneMap = ZoneSystemUtils.createLinkToZoneMap(network, zoneFinder);
+		this.nodeToZoneMap.putAll(nodeToZoneMap);
+		this.link2zone.putAll(linkToZoneMap);
+
+		for (Link link : network.getLinks().values()) {
+			zoneFinder.findZone(link.getToNode().getCoord()).ifPresent(zone -> {
+                List<Link> links = zoneToLinksMap.computeIfAbsent(zone.getId(), zoneId1 -> new ArrayList<>());
+                links.add(link);
+            });
+		}
 	}
 
 	/**
-	 * @param linkId
+	 * @param link
 	 * @return the the {@code DrtZone} that contains the {@code linkId}. If the 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.
 	 */
 	@Override
-	@Nullable
-	public Zone getZoneForLinkId(Id<Link> linkId) {
-		return link2zone.get(linkId);
+	public Optional<Zone> getZoneForLinkId(Id<Link> link) {
+		return Optional.ofNullable(link2zone.get(link));
+	}
+
+	@Override
+	public Optional<Zone> getZoneForNodeId(Id<Node> nodeId) {
+		return Optional.ofNullable(nodeToZoneMap.get(nodeId));
+	}
+
+	@Override
+	public List<Link> getLinksForZoneId(Id<Zone> zone) {
+		return zoneToLinksMap.getOrDefault(zone, Collections.emptyList());
 	}
 
 	/**
diff --git a/contribs/common/src/main/java/org/matsim/contrib/common/zones/ZoneSystemParams.java b/contribs/common/src/main/java/org/matsim/contrib/common/zones/ZoneSystemParams.java
new file mode 100644
index 00000000000..c52c39548f3
--- /dev/null
+++ b/contribs/common/src/main/java/org/matsim/contrib/common/zones/ZoneSystemParams.java
@@ -0,0 +1,12 @@
+package org.matsim.contrib.common.zones;
+
+import org.matsim.core.config.ReflectiveConfigGroup;
+
+/**
+ * @author nkuehnel / MOIA
+ */
+public abstract class ZoneSystemParams extends ReflectiveConfigGroup {
+	public ZoneSystemParams(String paramSetName) {
+		super(paramSetName);
+	}
+}
diff --git a/contribs/common/src/main/java/org/matsim/contrib/common/zones/ZoneSystemUtils.java b/contribs/common/src/main/java/org/matsim/contrib/common/zones/ZoneSystemUtils.java
index d1e3e6f2094..91f63ac8be0 100644
--- a/contribs/common/src/main/java/org/matsim/contrib/common/zones/ZoneSystemUtils.java
+++ b/contribs/common/src/main/java/org/matsim/contrib/common/zones/ZoneSystemUtils.java
@@ -1,27 +1,96 @@
 package org.matsim.contrib.common.zones;
 
+import com.google.common.base.Preconditions;
 import one.util.streamex.EntryStream;
 import one.util.streamex.StreamEx;
+import org.apache.commons.lang3.tuple.Pair;
 import org.locationtech.jts.geom.Point;
 import org.locationtech.jts.geom.prep.PreparedGeometry;
+import org.locationtech.jts.geom.prep.PreparedPolygon;
 import org.matsim.api.core.v01.Id;
+import org.matsim.api.core.v01.IdCollectors;
+import org.matsim.api.core.v01.IdMap;
+import org.matsim.api.core.v01.Identifiable;
 import org.matsim.api.core.v01.network.Link;
 import org.matsim.api.core.v01.network.Network;
+import org.matsim.api.core.v01.network.Node;
+import org.matsim.contrib.common.util.DistanceUtils;
+import org.matsim.contrib.common.zones.io.ZoneShpReader;
+import org.matsim.contrib.common.zones.io.ZoneXmlReader;
+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.common.zones.util.ZoneFinder;
+import org.matsim.contrib.common.zones.util.ZoneFinderImpl;
+import org.matsim.core.config.ConfigGroup;
 import org.matsim.core.utils.geometry.geotools.MGC;
 
+import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
+import java.io.File;
+import java.io.UncheckedIOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.*;
+import java.util.function.BinaryOperator;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
-import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.*;
+import static org.matsim.utils.gis.shp2matsim.ShpGeometryUtils.loadPreparedPolygons;
 
+/**
+ * @author nkuehnel / MOIA
+ */
 public final class ZoneSystemUtils {
 
 
 	private ZoneSystemUtils() {}
 
-	public static ZoneSystem createFromPreparedGeometries(Network network, Map<String, PreparedGeometry> geometries) {
+	public static ZoneSystem createZoneSystem(Network network, ZoneSystemParams zoneSystemParams) {
+		return createZoneSystem(null, network, zoneSystemParams, null);
+	}
+
+	public static ZoneSystem createZoneSystem(URL context, Network network, ZoneSystemParams zoneSystemParams) {
+		return createZoneSystem(context, network, zoneSystemParams, null);
+	}
+
+
+	public static ZoneSystem createZoneSystem(@Nullable URL context, @Nonnull Network network,
+											  @Nonnull ZoneSystemParams zoneSystemParams, @Nullable String crs) {
+
+		final ZoneSystem zoneSystem = switch (zoneSystemParams.getName()) {
+			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());
+			}
+			case SquareGridZoneSystemParams.SET_NAME -> {
+				Preconditions.checkNotNull(((SquareGridZoneSystemParams) zoneSystemParams).cellSize);
+
+				Predicate<Zone> zoneFilter = zone -> true;
+				SquareGridZoneSystem squareGridZoneSystem = new SquareGridZoneSystem(network, ((SquareGridZoneSystemParams) zoneSystemParams).cellSize, zoneFilter);
+				yield squareGridZoneSystem;
+			}
+			case H3GridZoneSystemParams.SET_NAME -> {
+				Preconditions.checkNotNull(((H3GridZoneSystemParams) zoneSystemParams).h3Resolution);
+				Preconditions.checkNotNull(crs);
+
+				Predicate<Zone> zoneFilter = zone -> true;
+				yield  new H3ZoneSystem(crs, ((H3GridZoneSystemParams) zoneSystemParams).h3Resolution, network, zoneFilter);
+			}
+			default -> throw new IllegalStateException("Unexpected value: " + zoneSystemParams.getName());
+		};
+		return zoneSystem;
+	}
+
+	public static ZoneSystem createFromPreparedGeometries(Network network, Map<String, PreparedPolygon> geometries) {
 
 		//geometries without links are skipped
 		Map<String, List<Link>> linksByGeometryId = StreamEx.of(network.getLinks().values())
@@ -30,11 +99,12 @@ public static ZoneSystem createFromPreparedGeometries(Network network, Map<Strin
 			.grouping(toList());
 
 		//the zonal system contains only zones that have at least one link
-		List<Zone> zones = EntryStream.of(linksByGeometryId)
-			.mapKeyValue((id, links) -> new ZoneImpl(Id.create(id, Zone.class), geometries.get(id), links))
-			.collect(toList());
+		Map<Id<Zone>, Zone> zones = EntryStream.of(linksByGeometryId)
+			.mapKeyValue((id, links) -> new ZoneImpl(Id.create(id, Zone.class), geometries.get(id), null))
+			.collect(IdCollectors.toIdMap(Zone.class, Identifiable::getId, zone -> zone));
+
 
-		return new ZoneSystemImpl(zones);
+		return new ZoneSystemImpl(zones.values(), new ZoneFinderImpl(zones), network);
 	}
 
 	/**
@@ -44,7 +114,7 @@ public static ZoneSystem createFromPreparedGeometries(Network network, Map<Strin
 	 * Result may be null in case the given link is outside of the service area.
 	 */
 	@Nullable
-	private static String getGeometryIdForLink(Link link, Map<String, PreparedGeometry> geometries) {
+	private static String getGeometryIdForLink(Link link, Map<String, PreparedPolygon> geometries) {
 		Point linkCoord = MGC.coord2Point(link.getToNode().getCoord());
 		return geometries.entrySet()
 			.stream()
@@ -61,4 +131,74 @@ public static Id<Zone> createZoneId(String id) {
 	public static Id<Zone> createZoneId(long id) {
 		return Id.create(id, Zone.class);
 	}
+
+	public static Map<Id<Zone>, Zone> readZones(String zonesXmlFile, String zonesShpFile) {
+		try {
+			return readZones(new File(zonesXmlFile).toURI().toURL(), new File(zonesShpFile).toURI().toURL());
+		} catch (MalformedURLException e) {
+			throw new UncheckedIOException(e);
+		}
+	}
+
+	public static Map<Id<Zone>, Zone> readZones(URL zonesXmlUrl, URL zonesShpUrl) {
+		ZoneXmlReader xmlReader = new ZoneXmlReader();
+		xmlReader.readURL(zonesXmlUrl);
+		Map<Id<Zone>, Zone> zones = xmlReader.getZones();
+
+		ZoneShpReader shpReader = new ZoneShpReader(zones);
+		shpReader.readZones(zonesShpUrl);
+		return zones;
+	}
+
+	// if CRSs of the network and zones are different, zoneFinder should convert between CRSs
+	public static IdMap<Link, Zone> createLinkToZoneMap(Network network, ZoneFinder zoneFinder) {
+		return EntryStream.of(network.getLinks())
+			.mapValues(link -> zoneFinder.findZone(link.getToNode().getCoord()))
+			.filterValues(Optional::isPresent)
+			.mapValues(Optional::get)
+			.collect(IdCollectors.toIdMap(Link.class, Map.Entry::getKey, Map.Entry::getValue));
+	}
+
+	public static IdMap<Node, Zone> createNodeToZoneMap(Network network, ZoneFinder zoneFinder) {
+		return EntryStream.of(network.getNodes())
+			.mapValues(node -> zoneFinder.findZone(node.getCoord()))
+			.filterValues(Optional::isPresent)
+			.mapValues(Optional::get)
+			.collect(IdCollectors.toIdMap(Node.class, Map.Entry::getKey, Map.Entry::getValue));
+
+	}
+
+	public static Set<Zone> filterZonesWithNodes(Collection<? extends Node> nodes, ZoneSystem zoneSystem) {
+		return nodes.stream().map(node -> zoneSystem.getZoneForNodeId(node.getId())).filter(Optional::isPresent).map(Optional::get).collect(toSet());
+	}
+
+	public static List<Node> selectNodesWithinArea(Collection<? extends Node> nodes, List<PreparedGeometry> areaGeoms) {
+		return nodes.stream().filter(node -> {
+			Point point = MGC.coord2Point(node.getCoord());
+			return areaGeoms.stream().anyMatch(serviceArea -> serviceArea.intersects(point));
+		}).collect(toList());
+	}
+
+	public static Map<Zone, Node> computeMostCentralNodes(Collection<? extends Node> nodes, ZoneSystem zoneSystem) {
+		BinaryOperator<Node> chooseMoreCentralNode = (n1, n2) -> {
+			Zone zone = zoneSystem.getZoneForNodeId(n1.getId()).orElseThrow();
+			return DistanceUtils.calculateSquaredDistance(n1, zone) <= DistanceUtils.calculateSquaredDistance(n2,
+					zone) ? n1 : n2;
+		};
+		return nodes.stream()
+				.map(n -> Pair.of(n, zoneSystem.getZoneForNodeId(n.getId()).orElseThrow()))
+				.collect(toMap(Pair::getValue, Pair::getKey, chooseMoreCentralNode));
+	}
+
+	public static IdMap<Zone, List<Zone>> initZonesByDistance(Map<Id<Zone>, Zone> zones) {
+		IdMap<Zone, List<Zone>> zonesByDistance = new IdMap<>(Zone.class);
+		for (final Zone currentZone : zones.values()) {
+			List<Zone> sortedZones = zones.values()
+					.stream()
+					.sorted(Comparator.comparing(z -> DistanceUtils.calculateSquaredDistance(currentZone, z)))
+					.collect(Collectors.toList());
+			zonesByDistance.put(currentZone.getId(), sortedZones);
+		}
+		return zonesByDistance;
+	}
 }
diff --git a/contribs/common/src/main/java/org/matsim/contrib/common/zones/h3/H3GridUtils.java b/contribs/common/src/main/java/org/matsim/contrib/common/zones/h3/H3GridUtils.java
deleted file mode 100644
index 9139e56442b..00000000000
--- a/contribs/common/src/main/java/org/matsim/contrib/common/zones/h3/H3GridUtils.java
+++ /dev/null
@@ -1,112 +0,0 @@
-package org.matsim.contrib.common.zones.h3;
-
-import com.uber.h3core.AreaUnit;
-import com.uber.h3core.H3Core;
-import com.uber.h3core.LengthUnit;
-import com.uber.h3core.util.LatLng;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.GeometryFactory;
-import org.locationtech.jts.geom.Polygon;
-import org.locationtech.jts.geom.prep.PreparedGeometry;
-import org.locationtech.jts.geom.prep.PreparedGeometryFactory;
-import org.matsim.api.core.v01.Coord;
-import org.matsim.api.core.v01.network.Network;
-import org.matsim.core.network.NetworkUtils;
-import org.matsim.core.utils.geometry.CoordUtils;
-import org.matsim.core.utils.geometry.CoordinateTransformation;
-import org.matsim.core.utils.geometry.transformations.TransformationFactory;
-
-import java.util.*;
-import java.util.stream.Collectors;
-
-/**
- * @author nkuehnel / MOIA
- */
-public class H3GridUtils {
-
-	static final Logger log = LogManager.getLogger(H3GridUtils.class);
-
-	public static Map<String, PreparedGeometry> createH3GridFromNetwork(Network network, int resolution, String crs) {
-
-		H3Core h3 = H3Utils.getInstance();
-
-		log.info("start creating H3 grid from network at resolution " + resolution);
-		double hexagonEdgeLengthAvg = h3.getHexagonEdgeLengthAvg(resolution, LengthUnit.m);
-		log.info("Average edge length: " + hexagonEdgeLengthAvg + " meters.");
-		log.info("Average centroid distance: " + hexagonEdgeLengthAvg * Math.sqrt(3) + " meters.");
-		log.info("Average hexagon area: " + h3.getHexagonAreaAvg(resolution, AreaUnit.m2) + " m^2");
-
-		double[] boundingbox = NetworkUtils.getBoundingBox(network.getNodes().values());
-		double minX = boundingbox[0];
-		double maxX = boundingbox[2];
-		double minY = boundingbox[1];
-		double maxY = boundingbox[3];
-
-		GeometryFactory gf = new GeometryFactory();
-		PreparedGeometryFactory preparedGeometryFactory = new PreparedGeometryFactory();
-		Map<String, PreparedGeometry> grid = new HashMap<>();
-		CoordinateTransformation toLatLong = TransformationFactory.getCoordinateTransformation(crs, TransformationFactory.WGS84);
-		CoordinateTransformation fromLatLong = TransformationFactory.getCoordinateTransformation(TransformationFactory.WGS84, crs);
-
-		List<LatLng> boundingBoxPoints = new ArrayList<>();
-
-		Coord bottomLeft = toLatLong.transform(new Coord(minX, minY));
-		Coord topLeft = toLatLong.transform(new Coord(minX, maxY));
-		Coord topRight = toLatLong.transform(new Coord(maxX, maxY));
-		Coord bottomRight = toLatLong.transform(new Coord(maxX, minY));
-
-		boundingBoxPoints.add(coordToLatLng(bottomLeft));
-		boundingBoxPoints.add(coordToLatLng(topLeft));
-		boundingBoxPoints.add(coordToLatLng(topRight));
-		boundingBoxPoints.add(coordToLatLng(bottomRight));
-		boundingBoxPoints.add(coordToLatLng(bottomLeft));
-
-		long millis = System.currentTimeMillis();
-
-		//get cells in a finer resolution to catch links at the border
-		List<String> h3Grid = h3.polygonToCellAddresses(boundingBoxPoints, Collections.emptyList(), Math.min(H3Utils.MAX_RES, resolution));
-		h3Grid = h3Grid
-			.parallelStream()
-			//also include neighbors with distance 1
-			.flatMap(h3Id -> h3.gridDisk(h3Id, 1).stream())
-			.distinct()
-			.toList();
-
-		if(h3Grid.isEmpty()) {
-			// bounding box too small to cover even a single H3 cell for a significant part. Use bounding box coords directly.
-			h3Grid = boundingBoxPoints.stream().map(corner -> h3.latLngToCellAddress(corner.lat, corner.lng, resolution)).distinct().toList();
-		}
-
-		log.info("Obtained " + h3Grid.size() + " H3 cells in " + (System.currentTimeMillis() - millis) + " ms.");
-
-
-		for (String h3Id : h3Grid) {
-			List<Coordinate> coordinateList = h3.cellToBoundary(h3Id)
-				.stream()
-				.map(latLng -> CoordUtils.createGeotoolsCoordinate(fromLatLong.transform(latLngToCoord(latLng))))
-				.collect(Collectors.toList());
-
-			if (!coordinateList.isEmpty()) {
-				coordinateList.add(coordinateList.get(0));
-			}
-
-			Polygon polygon = new Polygon(gf.createLinearRing(coordinateList.toArray(new Coordinate[0])), null, gf);
-			grid.put(h3Id, preparedGeometryFactory.create(polygon));
-		}
-
-		log.info("finished creating H3 grid from network.");
-		return grid;
-	}
-
-	public static LatLng coordToLatLng(Coord coord) {
-		//invert coordinate order
-		return new LatLng(coord.getY(), coord.getX());
-	}
-
-	public static Coord latLngToCoord(LatLng latLng) {
-		//invert coordinate order
-		return new Coord(latLng.lng, latLng.lat);
-	}
-}
diff --git a/contribs/common/src/main/java/org/matsim/contrib/common/zones/h3/H3Utils.java b/contribs/common/src/main/java/org/matsim/contrib/common/zones/h3/H3Utils.java
deleted file mode 100644
index 3bc44478ebf..00000000000
--- a/contribs/common/src/main/java/org/matsim/contrib/common/zones/h3/H3Utils.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package org.matsim.contrib.common.zones.h3;
-
-import com.uber.h3core.H3Core;
-
-import java.io.IOException;
-
-/**
- * @author nkuehnel / MOIA
- */
-public final class H3Utils {
-
-	private static H3Core h3;
-
-	public final static int MAX_RES = 16;
-
-
-	public static H3Core getInstance() {
-		if(h3 == null) {
-			try {
-				h3 = H3Core.newInstance();
-			} catch (IOException e) {
-				throw new RuntimeException(e);
-			}
-		}
-		return h3;
-	}
-}
diff --git a/contribs/common/src/main/java/org/matsim/contrib/common/zones/h3/H3ZoneSystemUtils.java b/contribs/common/src/main/java/org/matsim/contrib/common/zones/h3/H3ZoneSystemUtils.java
deleted file mode 100644
index f496d62cc5d..00000000000
--- a/contribs/common/src/main/java/org/matsim/contrib/common/zones/h3/H3ZoneSystemUtils.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package org.matsim.contrib.common.zones.h3;
-
-import com.uber.h3core.H3Core;
-import com.uber.h3core.util.LatLng;
-import one.util.streamex.EntryStream;
-import one.util.streamex.StreamEx;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.locationtech.jts.geom.prep.PreparedGeometry;
-import org.matsim.api.core.v01.Id;
-import org.matsim.api.core.v01.network.Link;
-import org.matsim.api.core.v01.network.Network;
-import org.matsim.contrib.common.zones.Zone;
-import org.matsim.contrib.common.zones.ZoneImpl;
-import org.matsim.contrib.common.zones.ZoneSystem;
-import org.matsim.contrib.common.zones.ZoneSystemImpl;
-import org.matsim.core.utils.geometry.CoordinateTransformation;
-import org.matsim.core.utils.geometry.transformations.TransformationFactory;
-
-import javax.annotation.Nullable;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-
-import static java.util.stream.Collectors.toList;
-
-/**
- * @author nkuehnel / MOIA
- */
-public class H3ZoneSystemUtils {
-
-	static final Logger log = LogManager.getLogger(H3ZoneSystemUtils.class);
-
-
-	public static ZoneSystem createFromPreparedGeometries(Network network, Map<String, PreparedGeometry> geometries,
-														  String crs, int resolution) {
-
-		//geometries without links are skipped
-		CoordinateTransformation ct = TransformationFactory.getCoordinateTransformation(crs, TransformationFactory.WGS84);
-		Map<String, List<Link>> linksByGeometryId = StreamEx.of(network.getLinks().values())
-			.mapToEntry(l -> getGeometryIdForLink(l, geometries, resolution, ct), l -> l)
-			.filterKeys(Objects::nonNull)
-			.grouping(toList());
-
-		log.info("Network filtered zone system contains " + linksByGeometryId.size() + " zones for "
-			+ network.getLinks().size() + " links and " + network.getNodes().size() + " nodes.");
-
-		//the zonal system contains only zones that have at least one link
-		List<Zone> zones = EntryStream.of(linksByGeometryId)
-			.mapKeyValue((id, links) -> new ZoneImpl(Id.create(id, Zone.class), geometries.get(id), links) {
-			})
-			.collect(toList());
-
-		return new ZoneSystemImpl(zones);
-	}
-
-	/**
-	 * @param ct
-	 * @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.
-	 * <p>
-	 * Careful: does not work if grid contains different levels of h3 resolutions.
-	 */
-	@Nullable
-	private static String getGeometryIdForLink(Link link, Map<String, PreparedGeometry> geometries, int resolution, CoordinateTransformation ct) {
-		H3Core h3 = H3Utils.getInstance();
-		LatLng latLng = H3GridUtils.coordToLatLng(ct.transform(link.getToNode().getCoord()));
-		String s = h3.latLngToCellAddress(latLng.lat, latLng.lng, resolution);
-		if (geometries.containsKey(s)) {
-			return s;
-		} else {
-			return null;
-		}
-	}
-}
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/io/ZoneShpReader.java b/contribs/common/src/main/java/org/matsim/contrib/common/zones/io/ZoneShpReader.java
similarity index 87%
rename from contribs/dvrp/src/main/java/org/matsim/contrib/zone/io/ZoneShpReader.java
rename to contribs/common/src/main/java/org/matsim/contrib/common/zones/io/ZoneShpReader.java
index d47dac4c588..46e6978a184 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/io/ZoneShpReader.java
+++ b/contribs/common/src/main/java/org/matsim/contrib/common/zones/io/ZoneShpReader.java
@@ -17,18 +17,20 @@
  *                                                                         *
  * *********************************************************************** */
 
-package org.matsim.contrib.zone.io;
-
-import java.net.URL;
-import java.util.Collection;
-import java.util.Map;
+package org.matsim.contrib.common.zones.io;
 
 import org.locationtech.jts.geom.MultiPolygon;
+import org.locationtech.jts.geom.prep.PreparedPolygon;
 import org.matsim.api.core.v01.Id;
-import org.matsim.contrib.zone.Zone;
+import org.matsim.contrib.common.zones.Zone;
+import org.matsim.contrib.common.zones.ZoneImpl;
 import org.matsim.core.utils.gis.GeoFileReader;
 import org.opengis.feature.simple.SimpleFeature;
 
+import java.net.URL;
+import java.util.Collection;
+import java.util.Map;
+
 public class ZoneShpReader {
 	private final Map<Id<Zone>, Zone> zones;
 
@@ -49,7 +51,9 @@ public void readZones(URL url, String idHeader) {
 		for (SimpleFeature ft : features) {
 			String id = ft.getAttribute(idHeader).toString();
 			Zone z = zones.get(Id.create(id, Zone.class));
-			z.setMultiPolygon((MultiPolygon)ft.getDefaultGeometry());
+			if(z instanceof ZoneImpl zImpl) {
+				zImpl.setGeometry(new PreparedPolygon((MultiPolygon) ft.getDefaultGeometry()));
+			}
 		}
 	}
 }
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/io/ZoneShpWriter.java b/contribs/common/src/main/java/org/matsim/contrib/common/zones/io/ZoneShpWriter.java
similarity index 83%
rename from contribs/dvrp/src/main/java/org/matsim/contrib/zone/io/ZoneShpWriter.java
rename to contribs/common/src/main/java/org/matsim/contrib/common/zones/io/ZoneShpWriter.java
index ba5bb104c9d..68ae03a7b6d 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/io/ZoneShpWriter.java
+++ b/contribs/common/src/main/java/org/matsim/contrib/common/zones/io/ZoneShpWriter.java
@@ -17,17 +17,21 @@
  *                                                                         *
  * *********************************************************************** */
 
-package org.matsim.contrib.zone.io;
-
-import java.util.*;
+package org.matsim.contrib.common.zones.io;
 
+import org.locationtech.jts.geom.prep.PreparedGeometry;
 import org.matsim.api.core.v01.Id;
-import org.matsim.contrib.zone.Zone;
+import org.matsim.contrib.common.zones.Zone;
 import org.matsim.core.utils.geometry.geotools.MGC;
-import org.matsim.core.utils.gis.*;
+import org.matsim.core.utils.gis.GeoFileWriter;
+import org.matsim.core.utils.gis.PolygonFeatureFactory;
 import org.opengis.feature.simple.SimpleFeature;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
 public class ZoneShpWriter {
 	public static final String ID_HEADER = "ID";
 
@@ -48,7 +52,7 @@ public void write(String shpFile) {
 		List<SimpleFeature> features = new ArrayList<>();
 		for (Zone z : zones.values()) {
 			String id = z.getId() + "";
-			features.add(factory.createPolygon(z.getMultiPolygon(), new Object[] { id }, id));
+			features.add(factory.createPolygon(z.getPreparedGeometry().getGeometry().getCoordinates(), new Object[] { id }, id));
 		}
 
 		GeoFileWriter.writeGeometries(features, shpFile);
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/io/ZoneXmlReader.java b/contribs/common/src/main/java/org/matsim/contrib/common/zones/io/ZoneXmlReader.java
similarity index 87%
rename from contribs/dvrp/src/main/java/org/matsim/contrib/zone/io/ZoneXmlReader.java
rename to contribs/common/src/main/java/org/matsim/contrib/common/zones/io/ZoneXmlReader.java
index 5be277bb3f7..fc3c7ad971b 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/io/ZoneXmlReader.java
+++ b/contribs/common/src/main/java/org/matsim/contrib/common/zones/io/ZoneXmlReader.java
@@ -17,15 +17,19 @@
  *                                                                         *
  * *********************************************************************** */
 
-package org.matsim.contrib.zone.io;
-
-import java.util.*;
+package org.matsim.contrib.common.zones.io;
 
 import org.matsim.api.core.v01.Id;
-import org.matsim.contrib.zone.Zone;
+import org.matsim.contrib.common.zones.Zone;
+import org.matsim.contrib.common.zones.ZoneImpl;
 import org.matsim.core.utils.io.MatsimXmlParser;
 import org.xml.sax.Attributes;
 
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Stack;
+
 public class ZoneXmlReader extends MatsimXmlParser {
 	private final static String ZONE = "zone";
 
@@ -53,6 +57,6 @@ public void endTag(String name, String content, Stack<String> context) {
 	private void startZone(Attributes atts) {
 		Id<Zone> id = Id.create(atts.getValue("id"), Zone.class);
 		String type = atts.getValue("type");
-		zones.put(id, new Zone(id, type));
+		zones.put(id, new ZoneImpl(id, null, null, type));
 	}
 }
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/io/ZoneXmlWriter.java b/contribs/common/src/main/java/org/matsim/contrib/common/zones/io/ZoneXmlWriter.java
similarity index 90%
rename from contribs/dvrp/src/main/java/org/matsim/contrib/zone/io/ZoneXmlWriter.java
rename to contribs/common/src/main/java/org/matsim/contrib/common/zones/io/ZoneXmlWriter.java
index f8f0a5b6180..f38912de946 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/io/ZoneXmlWriter.java
+++ b/contribs/common/src/main/java/org/matsim/contrib/common/zones/io/ZoneXmlWriter.java
@@ -1,15 +1,16 @@
-package org.matsim.contrib.zone.io;
+package org.matsim.contrib.common.zones.io;
+
+import org.matsim.api.core.v01.Id;
+
+import org.matsim.contrib.common.zones.Zone;
+import org.matsim.core.utils.collections.Tuple;
+import org.matsim.core.utils.io.MatsimXmlWriter;
 
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
-import org.matsim.api.core.v01.Id;
-import org.matsim.contrib.zone.Zone;
-import org.matsim.core.utils.collections.Tuple;
-import org.matsim.core.utils.io.MatsimXmlWriter;
-
 public class ZoneXmlWriter extends MatsimXmlWriter {
 	private final Map<Id<Zone>, Zone> zones;
 
diff --git a/contribs/common/src/main/java/org/matsim/contrib/common/zones/systems/grid/GISFileZoneSystemParams.java b/contribs/common/src/main/java/org/matsim/contrib/common/zones/systems/grid/GISFileZoneSystemParams.java
new file mode 100644
index 00000000000..71274cfccf9
--- /dev/null
+++ b/contribs/common/src/main/java/org/matsim/contrib/common/zones/systems/grid/GISFileZoneSystemParams.java
@@ -0,0 +1,31 @@
+package org.matsim.contrib.common.zones.systems.grid;
+
+import com.google.common.base.Verify;
+import org.matsim.contrib.common.zones.ZoneSystemParams;
+import org.matsim.core.config.Config;
+
+import javax.annotation.Nullable;
+
+/**
+ * @author nkuehnel / MOIA
+ */
+public class GISFileZoneSystemParams extends ZoneSystemParams {
+
+	public static final String SET_NAME = "GISFileZoneSystem";
+
+	public GISFileZoneSystemParams() {
+		super(SET_NAME);
+	}
+
+	@Parameter
+	@Comment("allows to configure zones. Used with zonesGeneration=ShapeFile")
+	@Nullable
+	public String zonesShapeFile = null;
+
+	@Override
+	protected void checkConsistency(Config config) {
+		super.checkConsistency(config);
+
+		Verify.verify(zonesShapeFile != null, "GIS zone input file must not be null.");
+	}
+}
diff --git a/contribs/common/src/main/java/org/matsim/contrib/common/zones/systems/grid/h3/H3GridZoneSystemParams.java b/contribs/common/src/main/java/org/matsim/contrib/common/zones/systems/grid/h3/H3GridZoneSystemParams.java
new file mode 100644
index 00000000000..e342419328d
--- /dev/null
+++ b/contribs/common/src/main/java/org/matsim/contrib/common/zones/systems/grid/h3/H3GridZoneSystemParams.java
@@ -0,0 +1,35 @@
+package org.matsim.contrib.common.zones.systems.grid.h3;
+
+import com.google.common.base.Verify;
+import org.matsim.contrib.common.zones.ZoneSystemParams;
+import org.matsim.core.config.Config;
+
+import javax.annotation.Nullable;
+
+
+/**
+ * @author nkuehnel / MOIA
+ */
+public class H3GridZoneSystemParams extends ZoneSystemParams {
+
+	public static final String SET_NAME = "H3GridZoneSystem";
+
+	public H3GridZoneSystemParams() {
+		super(SET_NAME);
+	}
+
+	@Parameter
+	@Comment("allows to configure H3 hexagonal zones. Used with zonesGeneration=H3. " +
+		"Range from 0 (122 cells worldwide) to 15 (569 E^12 cells). " +
+		"Usually meaningful between resolution 6 (3.7 km avg edge length) " +
+		"and 10 (70 m avg edge length). ")
+	@Nullable
+	public Integer h3Resolution = null;
+
+	@Override
+	protected void checkConsistency(Config config) {
+		super.checkConsistency(config);
+
+		Verify.verify(h3Resolution != null && h3Resolution >= 0 && h3Resolution < 15, "H3 resolution must be a valid level between 0 and 15.");
+	}
+}
diff --git a/contribs/common/src/main/java/org/matsim/contrib/common/zones/systems/grid/h3/H3Utils.java b/contribs/common/src/main/java/org/matsim/contrib/common/zones/systems/grid/h3/H3Utils.java
new file mode 100644
index 00000000000..ce6d71d86af
--- /dev/null
+++ b/contribs/common/src/main/java/org/matsim/contrib/common/zones/systems/grid/h3/H3Utils.java
@@ -0,0 +1,106 @@
+package org.matsim.contrib.common.zones.systems.grid.h3;
+
+import com.google.common.base.Preconditions;
+import com.uber.h3core.H3Core;
+import com.uber.h3core.util.LatLng;
+import org.locationtech.jts.geom.Polygon;
+import org.locationtech.jts.geom.prep.PreparedPolygon;
+import org.matsim.api.core.v01.Coord;
+import org.matsim.api.core.v01.Id;
+import org.matsim.contrib.common.zones.Zone;
+import org.matsim.contrib.common.zones.ZoneImpl;
+import org.matsim.core.utils.geometry.CoordinateTransformation;
+import org.matsim.core.utils.geometry.GeometryUtils;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * @author nkuehnel / MOIA
+ */
+public final class H3Utils {
+
+	private static H3Core h3;
+
+    static {
+        try {
+            h3 = H3Core.newInstance();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public final static int MAX_RES = 16;
+
+
+	public static H3Core getInstance() {
+		if(h3 == null) {
+			try {
+				h3 = H3Core.newInstance();
+			} catch (IOException e) {
+				throw new RuntimeException(e);
+			}
+		}
+		return h3;
+	}
+
+	private static Polygon getPolygon(String h3Id, CoordinateTransformation fromLatLong) {
+		Preconditions.checkArgument(h3.isValidCell(h3Id), "Not a valid H3 address: " + h3Id);
+		List<Coord> coordinateList = h3.cellToBoundary(h3Id)
+			.stream()
+			.map(latLng -> fromLatLong.transform(latLngToCoord(latLng)))
+			.collect(Collectors.toList());
+
+		if (!coordinateList.isEmpty()) {
+			coordinateList.add(coordinateList.get(0));
+		}
+
+        return GeometryUtils.createGeotoolsPolygon(coordinateList);
+	}
+
+	private static Polygon getPolygon(long h3Id, CoordinateTransformation fromLatLong) {
+		Preconditions.checkArgument(h3.isValidCell(h3Id), "Not a valid H3 address: " + h3Id);
+		List<Coord> coordinateList = h3.cellToBoundary(h3Id)
+			.stream()
+			.map(latLng -> fromLatLong.transform(latLngToCoord(latLng)))
+			.collect(Collectors.toList());
+
+		if (!coordinateList.isEmpty()) {
+			coordinateList.add(coordinateList.get(0));
+		}
+
+        return GeometryUtils.createGeotoolsPolygon(coordinateList);
+	}
+
+	public static LatLng coordToLatLng(Coord coord) {
+		//invert coordinate order
+		return new LatLng(coord.getY(), coord.getX());
+	}
+
+	public static Coord latLngToCoord(LatLng latLng) {
+		//invert coordinate order
+		return new Coord(latLng.lng, latLng.lat);
+	}
+
+	public static String getH3Address(Coord latLong, int resolution) {
+		LatLng latLng = coordToLatLng(latLong);
+		return h3.latLngToCellAddress(latLng.lat, latLng.lng, resolution);
+
+	}
+
+	public static long getH3Cell(Coord latLong, int resolution) {
+		LatLng latLng = coordToLatLng(latLong);
+		return h3.latLngToCell(latLng.lat, latLng.lng, resolution);
+
+	}
+
+	public static Optional<Zone> createZone(long id, CoordinateTransformation fromLatLong) {
+		if(h3.isValidCell(id)) {
+			return Optional.of(new ZoneImpl(Id.create(id, Zone.class), new PreparedPolygon(getPolygon(id, fromLatLong)), "h3"));
+		} else {
+			return Optional.empty();
+		}
+	}
+}
diff --git a/contribs/common/src/main/java/org/matsim/contrib/common/zones/systems/grid/h3/H3ZoneSystem.java b/contribs/common/src/main/java/org/matsim/contrib/common/zones/systems/grid/h3/H3ZoneSystem.java
new file mode 100644
index 00000000000..e9c84bccc4a
--- /dev/null
+++ b/contribs/common/src/main/java/org/matsim/contrib/common/zones/systems/grid/h3/H3ZoneSystem.java
@@ -0,0 +1,109 @@
+package org.matsim.contrib.common.zones.systems.grid.h3;
+
+import gnu.trove.map.TObjectLongMap;
+import gnu.trove.map.hash.TObjectLongHashMap;
+import org.matsim.api.core.v01.Coord;
+import org.matsim.api.core.v01.Id;
+import org.matsim.api.core.v01.IdMap;
+import org.matsim.api.core.v01.network.Link;
+import org.matsim.api.core.v01.network.Network;
+import org.matsim.api.core.v01.network.Node;
+import org.matsim.contrib.common.zones.GridZoneSystem;
+import org.matsim.contrib.common.zones.Zone;
+import org.matsim.core.utils.geometry.CoordinateTransformation;
+import org.matsim.core.utils.geometry.transformations.TransformationFactory;
+
+import java.util.*;
+import java.util.function.Predicate;
+
+/**
+ * @author nkuehnel / MOIA
+ */
+public class H3ZoneSystem implements GridZoneSystem {
+
+	private final IdMap<Zone, Zone> zones = new IdMap<>(Zone.class);
+	private final IdMap<Zone, List<Link>> zoneToLinksMap = new IdMap<>(Zone.class);
+
+	private final TObjectLongMap<Coord> coordH3Cache = new TObjectLongHashMap<>();
+
+	private final CoordinateTransformation toLatLong;
+	private final CoordinateTransformation fromLatLong;
+
+	private final int resolution;
+	private final Network network;
+	private final Predicate<Zone> filter;
+
+	public H3ZoneSystem(String crs, int resolution, Network network, Predicate<Zone> filter) {
+        this.fromLatLong = TransformationFactory.getCoordinateTransformation(TransformationFactory.WGS84, crs);
+        this.toLatLong = TransformationFactory.getCoordinateTransformation(crs, TransformationFactory.WGS84);
+        this.resolution = resolution;
+		this.network = network;
+		this.filter = filter;
+		this.network.getLinks().values().forEach(l -> getZoneForCoord(l.getToNode().getCoord()));
+    }
+
+
+    @Override
+	public Optional<Zone> getZoneForCoord(Coord coord) {
+
+		long h3Address = getH3Cell(coord);
+		Id<Zone> zoneId = Id.create(h3Address, Zone.class);
+
+		if(zones.containsKey(zoneId)) {
+			return Optional.of(zones.get(zoneId));
+		} else {
+			Optional<Zone> zone = H3Utils.createZone(h3Address, fromLatLong);
+			if(zone.isPresent() && filter.test(zone.get())) {
+				initZone(zone.get(), h3Address);
+				return zone;
+			} else {
+				return Optional.empty();
+			}
+		}
+	}
+
+	private void initZone(Zone zone, long h3Address) {
+		if(filter.test(zone)) {
+			zones.put(zone.getId(), zone);
+			for (Link link : network.getLinks().values()) {
+				long linkH3Address = getH3Cell(link.getToNode().getCoord());
+
+				if (linkH3Address == h3Address) {
+					List<Link> links = zoneToLinksMap.computeIfAbsent(zone.getId(), id -> new ArrayList<>());
+					links.add(link);
+				}
+			}
+		}
+	}
+
+	private long getH3Cell(Coord coord) {
+		long h3Address;
+		if(coordH3Cache.containsKey(coord)) {
+			h3Address = coordH3Cache.get(coord);
+		} else {
+			h3Address = H3Utils.getH3Cell(toLatLong.transform(coord), resolution);
+			coordH3Cache.put(coord, h3Address);
+		}
+		return h3Address;
+	}
+
+	@Override
+	public Optional<Zone> getZoneForLinkId(Id<Link> link) {
+		return getZoneForCoord(network.getLinks().get(link).getToNode().getCoord());
+	}
+
+	@Override
+	public Optional<Zone> getZoneForNodeId(Id<Node> nodeId) {
+		return getZoneForCoord(network.getNodes().get(nodeId).getCoord());
+	}
+
+	@Override
+	public List<Link> getLinksForZoneId(Id<Zone> zone) {
+		return zoneToLinksMap.getOrDefault(zone, Collections.emptyList());
+	}
+
+	@Override
+	public Map<Id<Zone>, Zone> getZones() {
+		return Collections.unmodifiableMap(zones);
+	}
+}
diff --git a/contribs/common/src/main/java/org/matsim/contrib/common/zones/systems/grid/square/SquareGridZoneSystem.java b/contribs/common/src/main/java/org/matsim/contrib/common/zones/systems/grid/square/SquareGridZoneSystem.java
new file mode 100644
index 00000000000..f11fa58f614
--- /dev/null
+++ b/contribs/common/src/main/java/org/matsim/contrib/common/zones/systems/grid/square/SquareGridZoneSystem.java
@@ -0,0 +1,189 @@
+/* *********************************************************************** *
+ * project: org.matsim.*
+ *                                                                         *
+ * *********************************************************************** *
+ *                                                                         *
+ * copyright       : (C) 2015 by the members listed in the COPYING,        *
+ *                   LICENSE and WARRANTY file.                            *
+ * email           : info at matsim dot org                                *
+ *                                                                         *
+ * *********************************************************************** *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *   See also COPYING, LICENSE and WARRANTY file                           *
+ *                                                                         *
+ * *********************************************************************** */
+
+package org.matsim.contrib.common.zones.systems.grid.square;
+
+import com.google.common.base.Preconditions;
+import org.locationtech.jts.geom.Polygon;
+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.IdMap;
+import org.matsim.api.core.v01.network.Link;
+import org.matsim.api.core.v01.network.Network;
+import org.matsim.api.core.v01.network.Node;
+import org.matsim.contrib.common.zones.GridZoneSystem;
+import org.matsim.contrib.common.zones.Zone;
+import org.matsim.contrib.common.zones.ZoneImpl;
+import org.matsim.core.network.NetworkUtils;
+import org.matsim.core.utils.geometry.GeometryUtils;
+
+import java.util.*;
+import java.util.function.Predicate;
+
+public class SquareGridZoneSystem implements GridZoneSystem {
+
+	private final double cellSize;
+	private final Predicate<Zone> zoneFilter;
+
+	private double minX;
+	private double minY;
+	private double maxX;
+	private double maxY;
+
+	private final int cols;
+	private final int rows;
+
+    private final Zone[] internalZones;
+
+	private final IdMap<Zone, Zone> zones = new IdMap<>(Zone.class);
+
+	private final IdMap<Zone, List<Link>> zoneToLinksMap = new IdMap<>(Zone.class);
+	private final Network network;
+
+
+	public SquareGridZoneSystem(Network network, double cellSize) {
+		this(network, cellSize, true, z -> true);
+	}
+	public SquareGridZoneSystem(Network network, double cellSize, Predicate<Zone> zoneFilter) {
+		this(network, cellSize, true, zoneFilter);
+	}
+	public SquareGridZoneSystem(Network network, double cellSize, boolean filterByNetwork, Predicate<Zone> zoneFilter) {
+		this.zoneFilter = zoneFilter;
+		Preconditions.checkArgument(!network.getNodes().isEmpty(), "Cannot create SquareGrid if no nodes");
+
+		this.network = network;
+		this.cellSize = cellSize;
+
+		initBounds();
+
+        this.rows = Math.max(1, (int) Math.ceil((maxY - minY) / cellSize));
+		this.cols = Math.max(1, (int)Math.ceil((maxX - minX) / cellSize));
+		this.internalZones = new Zone[rows * cols +1];
+
+		if(filterByNetwork) {
+			network.getLinks().values().forEach(l -> getOrCreateZone(l.getToNode().getCoord()));
+		} else {
+			for (double lx = minX; lx < maxX; lx += cellSize) {
+				for (double by = minY; by < maxY; by += cellSize) {
+					Coord coord = new Coord(lx, by);
+					getOrCreateZone(coord);
+				}
+			}
+		}
+	}
+
+	@Override
+	public Map<Id<Zone>, Zone> getZones() {
+		return Collections.unmodifiableMap(zones);
+	}
+
+	@Override
+	public Optional<Zone> getZoneForLinkId(Id<Link> linkId) {
+		return getZoneForNodeId(network.getLinks().get(linkId).getToNode().getId());
+	}
+
+	@Override
+	public Optional<Zone> getZoneForNodeId(Id<Node> nodeId) {
+		return getOrCreateZone(network.getNodes().get(nodeId).getCoord());
+	}
+
+	@Override
+	public Optional<Zone> getZoneForCoord(Coord coord) {
+		return getOrCreateZone(coord);
+	}
+
+	@Override
+	public List<Link> getLinksForZoneId(Id<Zone> zone) {
+		return zoneToLinksMap.get(zone);
+	}
+
+	private Optional<Zone> getOrCreateZone(Coord coord) {
+		int index = getIndex(coord);
+		Zone zone = internalZones[index];
+		if (zone == null) {
+			int r = bin(coord.getY(), minY);
+			int c = bin(coord.getX(), minX);
+			PreparedPolygon geometry = getGeometry(r, c);
+			zone = new ZoneImpl(Id.create(index, Zone.class), geometry, "square");
+
+			if(zoneFilter.test(zone)) {
+				internalZones[index] = zone;
+				zones.put(zone.getId(), zone);
+
+				for (Link link : network.getLinks().values()) {
+					if (getIndex(link.getToNode().getCoord()) == index) {
+						List<Link> links = zoneToLinksMap.computeIfAbsent(zone.getId(), zoneId -> new ArrayList<>());
+						links.add(link);
+					}
+				}
+			} else {
+				return Optional.empty();
+			}
+		}
+		return Optional.of(zone);
+	}
+
+	private PreparedPolygon getGeometry(int r, int c) {
+		List<Coord> coords = new ArrayList<>();
+		coords.add(new Coord(minX + c * cellSize, minY + r * cellSize));
+		coords.add(new Coord(minX + (c + 1) * cellSize, minY + r * cellSize));
+		coords.add(new Coord(minX + (c + 1 ) * cellSize, minY + (r + 1) * cellSize));
+		coords.add(new Coord(minX + c * cellSize, minY + (r + 1) * cellSize));
+		Polygon polygon = GeometryUtils.createGeotoolsPolygon(coords);
+		return new PreparedPolygon(polygon);
+	}
+
+
+	// This method's content has been copied from NetworkImpl
+	private void initBounds() {
+		double[] boundingBox = NetworkUtils.getBoundingBox(network.getNodes().values());
+		minX = boundingBox[0];
+		minY = boundingBox[1];
+		maxX = boundingBox[2];
+		maxY = boundingBox[3];
+		// yy the above four lines are problematic if the coordinate values are much smaller than one. kai, oct'15
+	}
+
+
+	private int getIndex(Coord coord) {
+		Preconditions.checkArgument(coord.getX() >= minX, "Coord.x less than minX");
+		Preconditions.checkArgument(coord.getX() <= maxX, "Coord.x greater than maxX");
+		Preconditions.checkArgument(coord.getY() >= minY, "Coord.y less than minY");
+		Preconditions.checkArgument(coord.getY() <= maxY, "Coord.y greater than maxY");
+		int r;
+        int c;
+        if(coord.getY() == maxY) {
+			r = Math.max(0, rows - 1);
+		} else {
+        	r = bin(coord.getY(), minY);
+		}
+
+		if(coord.getX() == maxX) {
+			c = Math.max(0, cols - 1);
+		} else {
+			c = bin(coord.getX(), minX);
+		}
+		return r * cols + c;
+	}
+
+	private int bin(double coord, double minCoord) {
+		return (int)((coord - minCoord) / cellSize);
+	}
+}
diff --git a/contribs/common/src/main/java/org/matsim/contrib/common/zones/systems/grid/square/SquareGridZoneSystemParams.java b/contribs/common/src/main/java/org/matsim/contrib/common/zones/systems/grid/square/SquareGridZoneSystemParams.java
new file mode 100644
index 00000000000..3fe1fbe1b5b
--- /dev/null
+++ b/contribs/common/src/main/java/org/matsim/contrib/common/zones/systems/grid/square/SquareGridZoneSystemParams.java
@@ -0,0 +1,31 @@
+package org.matsim.contrib.common.zones.systems.grid.square;
+
+import com.google.common.base.Verify;
+import jakarta.validation.constraints.Positive;
+import org.matsim.contrib.common.zones.ZoneSystemParams;
+import org.matsim.core.config.Config;
+
+/**
+ * @author nkuehnel / MOIA
+ */
+public class SquareGridZoneSystemParams extends ZoneSystemParams {
+
+	public static final String SET_NAME = "SquareGridZoneSystem";
+
+	public SquareGridZoneSystemParams() {
+		super(SET_NAME);
+	}
+
+	@Parameter
+	@Comment("size of square cells used for demand aggregation."
+		+ " Depends on demand, supply and network. Often used with values in the range of 500 - 2000 m")
+	@Positive
+	public double cellSize = 200.;// [m]
+
+	@Override
+	protected void checkConsistency(Config config) {
+		super.checkConsistency(config);
+		Verify.verify(cellSize > 0 && Double.isFinite(cellSize), "cell size must be finite and positive.");
+	}
+
+}
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/util/SubzoneUtils.java b/contribs/common/src/main/java/org/matsim/contrib/common/zones/util/SubzoneUtils.java
similarity index 88%
rename from contribs/dvrp/src/main/java/org/matsim/contrib/zone/util/SubzoneUtils.java
rename to contribs/common/src/main/java/org/matsim/contrib/common/zones/util/SubzoneUtils.java
index a224e470ec6..01b9a80c6ee 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/util/SubzoneUtils.java
+++ b/contribs/common/src/main/java/org/matsim/contrib/common/zones/util/SubzoneUtils.java
@@ -17,38 +17,38 @@
  *                                                                         *
  * *********************************************************************** */
 
-package org.matsim.contrib.zone.util;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+package org.matsim.contrib.common.zones.util;
 
 import org.geotools.geometry.jts.GeometryCollector;
 import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.MultiPolygon;
 import org.locationtech.jts.geom.Polygon;
 import org.locationtech.jts.geom.TopologyException;
+import org.locationtech.jts.geom.prep.PreparedPolygon;
 import org.locationtech.jts.geom.util.PolygonExtracter;
 import org.matsim.api.core.v01.Id;
-import org.matsim.contrib.zone.Zone;
+import org.matsim.contrib.common.zones.Zone;
 import org.opengis.feature.simple.SimpleFeature;
 
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 public class SubzoneUtils {
 	public static Map<Id<Zone>, List<Polygon>> extractSubzonePolygons(Map<Id<Zone>, Zone> zones,
-			Collection<SimpleFeature> subzonePattern) {
+																	  Collection<SimpleFeature> subzonePattern) {
 		Map<Id<Zone>, List<Polygon>> polygonsByZone = new HashMap<>();
 		int topologyExceptionCount = 0;
 
 		for (Zone z : zones.values()) {
-			MultiPolygon zoneMultiPoly = z.getMultiPolygon();
+			PreparedPolygon zonePoly = z.getPreparedGeometry();
 			GeometryCollector geometryCollector = new GeometryCollector();
 
 			for (SimpleFeature f : subzonePattern) {
 				Geometry featureGeometry = (Geometry)f.getDefaultGeometry();
 
 				try {
-					geometryCollector.add(zoneMultiPoly.intersection(featureGeometry));
+					geometryCollector.add(zonePoly.getGeometry().intersection(featureGeometry));
 				} catch (TopologyException e) {
 					topologyExceptionCount++;
 				}
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/util/ZoneFinder.java b/contribs/common/src/main/java/org/matsim/contrib/common/zones/util/ZoneFinder.java
similarity index 89%
rename from contribs/dvrp/src/main/java/org/matsim/contrib/zone/util/ZoneFinder.java
rename to contribs/common/src/main/java/org/matsim/contrib/common/zones/util/ZoneFinder.java
index ac14584bd9c..95f0a9ce53c 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/util/ZoneFinder.java
+++ b/contribs/common/src/main/java/org/matsim/contrib/common/zones/util/ZoneFinder.java
@@ -17,11 +17,13 @@
  *                                                                         *
  * *********************************************************************** */
 
-package org.matsim.contrib.zone.util;
+package org.matsim.contrib.common.zones.util;
 
 import org.matsim.api.core.v01.Coord;
-import org.matsim.contrib.zone.Zone;
+import org.matsim.contrib.common.zones.Zone;
+
+import java.util.Optional;
 
 public interface ZoneFinder {
-	Zone findZone(Coord coord);
+	Optional<Zone> findZone(Coord coord);
 }
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/util/ZoneFinderImpl.java b/contribs/common/src/main/java/org/matsim/contrib/common/zones/util/ZoneFinderImpl.java
similarity index 80%
rename from contribs/dvrp/src/main/java/org/matsim/contrib/zone/util/ZoneFinderImpl.java
rename to contribs/common/src/main/java/org/matsim/contrib/common/zones/util/ZoneFinderImpl.java
index 17f0c8d0716..fbe32f45ac5 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/util/ZoneFinderImpl.java
+++ b/contribs/common/src/main/java/org/matsim/contrib/common/zones/util/ZoneFinderImpl.java
@@ -17,10 +17,11 @@
  *                                                                         *
  * *********************************************************************** */
 
-package org.matsim.contrib.zone.util;
+package org.matsim.contrib.common.zones.util;
 
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 import org.locationtech.jts.geom.Envelope;
 import org.locationtech.jts.geom.Point;
@@ -28,7 +29,7 @@
 import org.locationtech.jts.index.quadtree.Quadtree;
 import org.matsim.api.core.v01.Coord;
 import org.matsim.api.core.v01.Id;
-import org.matsim.contrib.zone.Zone;
+import org.matsim.contrib.common.zones.Zone;
 import org.matsim.core.utils.geometry.geotools.MGC;
 
 public class ZoneFinderImpl implements ZoneFinder {
@@ -39,19 +40,26 @@ public ZoneFinderImpl(Map<Id<Zone>, Zone> zones, double expansionDistance) {
 		this.expansionDistance = expansionDistance;
 
 		for (Zone z : zones.values()) {
-			quadTree.insert(z.getMultiPolygon().getEnvelopeInternal(), z);
+			quadTree.insert(z.getPreparedGeometry().getGeometry().getEnvelopeInternal(), z);
+		}
+	}
+	public ZoneFinderImpl(Map<Id<Zone>, Zone> zones) {
+		this.expansionDistance = Double.MIN_VALUE;
+
+		for (Zone z : zones.values()) {
+			quadTree.insert(z.getPreparedGeometry().getGeometry().getEnvelopeInternal(), z);
 		}
 	}
 
 	@Override
 	@SuppressWarnings("unchecked")
-	public Zone findZone(Coord coord) {
+	public Optional<Zone> findZone(Coord coord) {
 		Point point = MGC.coord2Point(coord);
 		Envelope env = point.getEnvelopeInternal();
 
 		Zone zone = getSmallestZoneContainingPoint(quadTree.query(env), point);
 		if (zone != null) {
-			return zone;
+			return Optional.of(zone);
 		}
 
 		if (expansionDistance > 0) {
@@ -59,7 +67,7 @@ public Zone findZone(Coord coord) {
 			zone = getNearestZone(quadTree.query(env), point);
 		}
 
-		return zone;
+		return Optional.ofNullable(zone);
 	}
 
 	private Zone getSmallestZoneContainingPoint(List<Zone> zones, Point point) {
@@ -71,8 +79,8 @@ private Zone getSmallestZoneContainingPoint(List<Zone> zones, Point point) {
 		Zone smallestZone = null;
 
 		for (Zone z : zones) {
-			if (z.getMultiPolygon().contains(point)) {
-				double area = z.getMultiPolygon().getArea();
+			if (z.getPreparedGeometry().contains(point)) {
+				double area = z.getPreparedGeometry().getGeometry().getArea();
 				if (area < minArea) {
 					minArea = area;
 					smallestZone = z;
@@ -92,7 +100,7 @@ private Zone getNearestZone(List<Zone> zones, Point point) {
 		Zone nearestZone = null;
 
 		for (Zone z : zones) {
-			double distance = z.getMultiPolygon().distance(point);
+			double distance = z.getPreparedGeometry().getGeometry().distance(point);
 			if (distance <= expansionDistance) {
 				if (distance < minDistance) {
 					minDistance = distance;
diff --git a/contribs/common/src/test/java/org/matsim/contrib/common/zones/systems/grid/SquareGridTest.java b/contribs/common/src/test/java/org/matsim/contrib/common/zones/systems/grid/SquareGridTest.java
new file mode 100644
index 00000000000..b22d2500b57
--- /dev/null
+++ b/contribs/common/src/test/java/org/matsim/contrib/common/zones/systems/grid/SquareGridTest.java
@@ -0,0 +1,112 @@
+/*
+ * *********************************************************************** *
+ * project: org.matsim.*
+ * *********************************************************************** *
+ *                                                                         *
+ * copyright       : (C) 2020 by the members listed in the COPYING,        *
+ *                   LICENSE and WARRANTY file.                            *
+ * email           : info at matsim dot org                                *
+ *                                                                         *
+ * *********************************************************************** *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *   See also COPYING, LICENSE and WARRANTY file                           *
+ *                                                                         *
+ * *********************************************************************** *
+ */
+
+package org.matsim.contrib.common.zones.systems.grid;
+
+import org.junit.jupiter.api.Test;
+import org.locationtech.jts.geom.CoordinateSequence;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.impl.CoordinateArraySequence;
+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.Network;
+import org.matsim.api.core.v01.network.Node;
+import org.matsim.contrib.common.zones.Zone;
+import org.matsim.contrib.common.zones.ZoneImpl;
+import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystem;
+import org.matsim.core.network.NetworkUtils;
+
+import static org.assertj.core.api.Assertions.*;
+
+/**
+ * @author Michal Maciejewski (michalm)
+ */
+public class SquareGridTest {
+	@Test
+	void emptyNodes_fail() {
+		assertThatThrownBy(() -> new SquareGridZoneSystem(NetworkUtils.createNetwork(), 100, zone -> true)).isExactlyInstanceOf(IllegalArgumentException.class)
+				.hasMessage("Cannot create SquareGrid if no nodes");
+	}
+
+	@Test
+	void outsideBoundaries_withinEpsilon_success() {
+		Node node_0_0 = node(0, 0);
+		Network network = NetworkUtils.createNetwork();
+		network.addNode(node_0_0);
+		SquareGridZoneSystem grid = new SquareGridZoneSystem(network, 100, zone -> true);
+		assertThatCode(() -> grid.getZoneForCoord(new Coord(-0, 0))).doesNotThrowAnyException();
+	}
+
+	@Test
+	void outsideBoundaries_outsideEpsilon_fail() {
+		Node node_0_0 = node(0, 0);
+		Network network = NetworkUtils.createNetwork();
+		network.addNode(node_0_0);
+		SquareGridZoneSystem grid = new SquareGridZoneSystem(network, 100, zone -> true);
+
+		assertThatThrownBy(() -> grid.getZoneForCoord(new Coord(-2, 0))).isExactlyInstanceOf(
+				IllegalArgumentException.class);
+	}
+
+	@Test
+	void testGrid() {
+		Node node_0_0 = node(0, 0);
+		Node node_150_150 = node(150, 150);
+
+		Network network = NetworkUtils.createNetwork();
+		network.addNode(node_0_0);
+		network.addNode(node_150_150);
+		SquareGridZoneSystem grid = new SquareGridZoneSystem(network, 100, zone -> true);
+
+		Coord coord0 = new Coord(100, 100);
+		CoordinateSequence coordinateSequence = getCoordinateSequence();
+
+		PreparedPolygon polygon = new PreparedPolygon(new GeometryFactory().createPolygon(coordinateSequence));
+		Zone zone0 = new ZoneImpl(Id.create(3, Zone.class), polygon, new Coord(150, 150), "square");
+		assertThat(grid.getZoneForCoord(coord0).orElseThrow()).isEqualToComparingFieldByFieldRecursively(zone0);
+
+
+	}
+
+	private static CoordinateSequence getCoordinateSequence() {
+		CoordinateSequence coordinateSequence = new CoordinateArraySequence(5);
+
+		coordinateSequence.setOrdinate(0,0, 100);
+		coordinateSequence.setOrdinate(0,1, 100);
+
+		coordinateSequence.setOrdinate(1,0, 200);
+		coordinateSequence.setOrdinate(1,1, 100);
+
+		coordinateSequence.setOrdinate(2,0, 200);
+		coordinateSequence.setOrdinate(2,1, 200);
+
+		coordinateSequence.setOrdinate(3,0, 100);
+		coordinateSequence.setOrdinate(3,1, 200);
+
+		coordinateSequence.setOrdinate(4,0, 100);
+		coordinateSequence.setOrdinate(4,1, 100);
+		return coordinateSequence;
+	}
+
+	private Node node(double x, double y) {
+		return NetworkUtils.createNode(Id.createNodeId(x + "," + y), new Coord(x, y));
+	}
+}
diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/companions/DrtCompanionParams.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/companions/DrtCompanionParams.java
index f3947244027..05bf57a35a1 100644
--- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/companions/DrtCompanionParams.java
+++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/companions/DrtCompanionParams.java
@@ -19,7 +19,7 @@
 
 package org.matsim.contrib.drt.extension.companions;
 
-import org.matsim.contrib.util.ReflectiveConfigGroupWithConfigurableParameterSets;
+import org.matsim.contrib.common.util.ReflectiveConfigGroupWithConfigurableParameterSets;
 import org.matsim.core.config.Config;
 import java.util.Arrays;
 import java.util.List;
diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/run/DrtEstimatorConfigGroup.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/run/DrtEstimatorConfigGroup.java
index 985435bcfc3..26a4e754538 100644
--- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/run/DrtEstimatorConfigGroup.java
+++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/run/DrtEstimatorConfigGroup.java
@@ -6,7 +6,7 @@
 import jakarta.validation.constraints.PositiveOrZero;
 import org.matsim.api.core.v01.TransportMode;
 import org.matsim.contrib.dvrp.run.Modal;
-import org.matsim.contrib.util.ReflectiveConfigGroupWithConfigurableParameterSets;
+import org.matsim.contrib.common.util.ReflectiveConfigGroupWithConfigurableParameterSets;
 
 public class DrtEstimatorConfigGroup extends ReflectiveConfigGroupWithConfigurableParameterSets implements Modal {
 
diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/DrtOperationsParams.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/DrtOperationsParams.java
index 43dbc38503e..32f1ac22b2b 100644
--- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/DrtOperationsParams.java
+++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/DrtOperationsParams.java
@@ -10,7 +10,7 @@
 
 import org.matsim.contrib.drt.extension.operations.operationFacilities.OperationFacilitiesParams;
 import org.matsim.contrib.drt.extension.operations.shifts.config.ShiftsParams;
-import org.matsim.contrib.util.ReflectiveConfigGroupWithConfigurableParameterSets;
+import org.matsim.contrib.common.util.ReflectiveConfigGroupWithConfigurableParameterSets;
 
 import javax.annotation.Nullable;
 import java.util.Optional;
diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/operationFacilities/OperationFacilitiesParams.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/operationFacilities/OperationFacilitiesParams.java
index 7f90b17861a..1d6454e2668 100644
--- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/operationFacilities/OperationFacilitiesParams.java
+++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/operationFacilities/OperationFacilitiesParams.java
@@ -1,6 +1,6 @@
 package org.matsim.contrib.drt.extension.operations.operationFacilities;
 
-import org.matsim.contrib.util.ReflectiveConfigGroupWithConfigurableParameterSets;
+import org.matsim.contrib.common.util.ReflectiveConfigGroupWithConfigurableParameterSets;
 import org.matsim.core.config.ConfigGroup;
 
 import java.net.URL;
diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/config/ShiftsParams.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/config/ShiftsParams.java
index 2f7944d5b9d..4d76cbddfc0 100644
--- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/config/ShiftsParams.java
+++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/config/ShiftsParams.java
@@ -9,7 +9,7 @@
 package org.matsim.contrib.drt.extension.operations.shifts.config;
 
 import org.matsim.contrib.ev.infrastructure.ChargerSpecification;
-import org.matsim.contrib.util.ReflectiveConfigGroupWithConfigurableParameterSets;
+import org.matsim.contrib.common.util.ReflectiveConfigGroupWithConfigurableParameterSets;
 import org.matsim.core.config.ConfigGroup;
 
 import java.net.URL;
diff --git a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/companions/RunDrtWithCompanionExampleIT.java b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/companions/RunDrtWithCompanionExampleIT.java
index 8a6b6e4098c..b0fa3afd2db 100644
--- a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/companions/RunDrtWithCompanionExampleIT.java
+++ b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/companions/RunDrtWithCompanionExampleIT.java
@@ -24,10 +24,13 @@
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 import org.matsim.api.core.v01.Id;
+import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystemParams;
 import org.matsim.contrib.drt.extension.DrtWithExtensionsConfigGroup;
 import org.matsim.contrib.drt.run.MultiModeDrtConfigGroup;
 import org.matsim.contrib.dvrp.run.DvrpConfigGroup;
+import org.matsim.contrib.zone.skims.DvrpTravelTimeMatrixParams;
 import org.matsim.core.config.Config;
+import org.matsim.core.config.ConfigGroup;
 import org.matsim.core.config.ConfigUtils;
 import org.matsim.core.controler.Controler;
 import org.matsim.core.controler.OutputDirectoryHierarchy.OverwriteFileSetting;
@@ -57,7 +60,8 @@ void testRunDrtWithCompanions() {
 		MatsimRandom.reset();
 		Id.resetCaches();
 		URL configUrl = IOUtils.extendUrl(ExamplesUtils.getTestScenarioURL("mielec"), "mielec_drt_config.xml");
-		Config config = ConfigUtils.loadConfig(configUrl, new OTFVisConfigGroup(), new MultiModeDrtConfigGroup(DrtWithExtensionsConfigGroup::new), new DvrpConfigGroup());
+		DvrpConfigGroup dvrpConfigGroup = new DvrpConfigGroup();
+		Config config = ConfigUtils.loadConfig(configUrl, new OTFVisConfigGroup(), new MultiModeDrtConfigGroup(DrtWithExtensionsConfigGroup::new), dvrpConfigGroup);
 
 		// Add DrtCompanionParams with some default values into existing Drt configurations
 		MultiModeDrtConfigGroup multiModeDrtConfigGroup = MultiModeDrtConfigGroup.get(config);
@@ -68,6 +72,10 @@ void testRunDrtWithCompanions() {
 
 		drtWithExtensionsConfigGroup.addParameterSet(crtCompanionParams);
 
+		DvrpTravelTimeMatrixParams matrixParams = dvrpConfigGroup.getTravelTimeMatrixParams();
+		ConfigGroup zoneSystemParams = matrixParams.createParameterSet(SquareGridZoneSystemParams.SET_NAME);
+		matrixParams.addParameterSet(zoneSystemParams);
+
 		config.controller().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists);
 		config.controller().setOutputDirectory(utils.getOutputDirectory());
 
@@ -84,7 +92,8 @@ void testRunDrtWithCompanionsMultiThreaded() {
 		MatsimRandom.reset();
 		Id.resetCaches();
 		URL configUrl = IOUtils.extendUrl(ExamplesUtils.getTestScenarioURL("mielec"), "mielec_drt_config.xml");
-		Config config = ConfigUtils.loadConfig(configUrl, new OTFVisConfigGroup(), new MultiModeDrtConfigGroup(DrtWithExtensionsConfigGroup::new), new DvrpConfigGroup());
+		DvrpConfigGroup dvrpConfigGroup = new DvrpConfigGroup();
+		Config config = ConfigUtils.loadConfig(configUrl, new OTFVisConfigGroup(), new MultiModeDrtConfigGroup(DrtWithExtensionsConfigGroup::new), dvrpConfigGroup);
 
 		// Add DrtCompanionParams with some default values into existing Drt configurations
 		MultiModeDrtConfigGroup multiModeDrtConfigGroup = MultiModeDrtConfigGroup.get(config);
@@ -95,6 +104,10 @@ void testRunDrtWithCompanionsMultiThreaded() {
 
 		drtWithExtensionsConfigGroup.addParameterSet(crtCompanionParams);
 
+		DvrpTravelTimeMatrixParams matrixParams = dvrpConfigGroup.getTravelTimeMatrixParams();
+		ConfigGroup zoneSystemParams = matrixParams.createParameterSet(SquareGridZoneSystemParams.SET_NAME);
+		matrixParams.addParameterSet(zoneSystemParams);
+
 		config.controller().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists);
 		config.controller().setOutputDirectory(utils.getOutputDirectory());
 		config.qsim().setNumberOfThreads(2);
diff --git a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/edrt/run/RunEDrtScenarioIT.java b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/edrt/run/RunEDrtScenarioIT.java
index 2ced56d6edf..1d23eb2a1c4 100644
--- a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/edrt/run/RunEDrtScenarioIT.java
+++ b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/edrt/run/RunEDrtScenarioIT.java
@@ -28,6 +28,7 @@
 import org.matsim.api.core.v01.Scenario;
 import org.matsim.api.core.v01.population.Plan;
 import org.matsim.api.core.v01.population.Population;
+import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystemParams;
 import org.matsim.contrib.drt.extension.edrt.optimizer.EDrtVehicleDataEntryFactory;
 import org.matsim.contrib.drt.prebooking.PrebookingParams;
 import org.matsim.contrib.drt.prebooking.logic.ProbabilityBasedPrebookingLogic;
@@ -48,7 +49,9 @@
 import org.matsim.contrib.evrp.EvDvrpFleetQSimModule;
 import org.matsim.contrib.evrp.OperatingVehicleProvider;
 import org.matsim.contrib.otfvis.OTFVisLiveModule;
+import org.matsim.contrib.zone.skims.DvrpTravelTimeMatrixParams;
 import org.matsim.core.config.Config;
+import org.matsim.core.config.ConfigGroup;
 import org.matsim.core.config.ConfigUtils;
 import org.matsim.core.controler.AbstractModule;
 import org.matsim.core.controler.Controler;
@@ -75,10 +78,10 @@ void test() {
 	void testMultiModeDrtDeterminism() {
 		URL configUrl = IOUtils.extendUrl(ExamplesUtils.getTestScenarioURL("mielec"), "mielec_multiModeEdrt_config.xml");
 
-		Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), new DvrpConfigGroup(),
+		DvrpConfigGroup dvrpConfigGroup = new DvrpConfigGroup();
+		Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), dvrpConfigGroup,
 			new OTFVisConfigGroup(), new EvConfigGroup());
 
-
 		Controler controller = RunEDrtScenario.createControler(config, false);
 		config.controller().setLastIteration(2);
 
@@ -87,7 +90,7 @@ void testMultiModeDrtDeterminism() {
 
 		controller.run();
 
-		assertEquals(2011, tracker.passengerPickupEvents);
+		assertEquals(1926, tracker.passengerPickupEvents);
 	}
 
 
@@ -95,7 +98,8 @@ void testMultiModeDrtDeterminism() {
 	void testWithPrebooking() {
 		URL configUrl = IOUtils.extendUrl(ExamplesUtils.getTestScenarioURL("mielec"), "mielec_edrt_config.xml");
 
-		Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), new DvrpConfigGroup(),
+		DvrpConfigGroup dvrpConfigGroup = new DvrpConfigGroup();
+		Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), dvrpConfigGroup,
 				new OTFVisConfigGroup(), new EvConfigGroup());
 
 		DrtConfigGroup drtConfig = DrtConfigGroup.getSingleModeDrtConfig(config);
diff --git a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/fiss/RunFissDrtScenarioIT.java b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/fiss/RunFissDrtScenarioIT.java
index 6034419d22e..d9eda86cc8b 100644
--- a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/fiss/RunFissDrtScenarioIT.java
+++ b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/fiss/RunFissDrtScenarioIT.java
@@ -5,7 +5,8 @@
 import org.matsim.api.core.v01.TransportMode;
 import org.matsim.api.core.v01.events.LinkLeaveEvent;
 import org.matsim.api.core.v01.events.handler.LinkLeaveEventHandler;
-import org.matsim.contrib.drt.analysis.zonal.DrtZonalSystemParams;
+import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystemParams;
+import org.matsim.contrib.drt.analysis.zonal.DrtZoneSystemParams;
 import org.matsim.contrib.drt.extension.operations.DrtOperationsControlerCreator;
 import org.matsim.contrib.drt.extension.operations.DrtOperationsParams;
 import org.matsim.contrib.drt.extension.operations.DrtWithOperationsConfigGroup;
@@ -17,6 +18,7 @@
 import org.matsim.contrib.drt.run.DrtConfigGroup;
 import org.matsim.contrib.drt.run.MultiModeDrtConfigGroup;
 import org.matsim.contrib.dvrp.run.DvrpConfigGroup;
+import org.matsim.contrib.zone.skims.DvrpTravelTimeMatrixParams;
 import org.matsim.core.config.Config;
 import org.matsim.core.config.ConfigGroup;
 import org.matsim.core.config.ConfigUtils;
@@ -75,16 +77,18 @@ void test() {
 
 		drtConfigGroup.getRebalancingParams().get().addParameterSet(strategyParams);
 
-		DrtZonalSystemParams drtZonalSystemParams = new DrtZonalSystemParams();
-		drtZonalSystemParams.zonesGeneration = DrtZonalSystemParams.ZoneGeneration.GridFromNetwork;
-		drtZonalSystemParams.cellSize = 500.;
-		drtZonalSystemParams.targetLinkSelection = DrtZonalSystemParams.TargetLinkSelection.mostCentral;
-		drtConfigGroup.addParameterSet(drtZonalSystemParams);
+		DrtZoneSystemParams drtZoneSystemParams = new DrtZoneSystemParams();
+		SquareGridZoneSystemParams zoneSystemParams = (SquareGridZoneSystemParams) drtZoneSystemParams.createParameterSet(SquareGridZoneSystemParams.SET_NAME);
+		zoneSystemParams.cellSize = 500.;
+		drtZoneSystemParams.addParameterSet(zoneSystemParams);
+		drtZoneSystemParams.targetLinkSelection = DrtZoneSystemParams.TargetLinkSelection.mostCentral;
+		drtConfigGroup.addParameterSet(drtZoneSystemParams);
 
 		multiModeDrtConfigGroup.addParameterSet(drtWithShiftsConfigGroup);
 
+		DvrpConfigGroup dvrpConfigGroup = new DvrpConfigGroup();
 		final Config config = ConfigUtils.createConfig(multiModeDrtConfigGroup,
-				new DvrpConfigGroup());
+			dvrpConfigGroup);
 		config.setContext(ExamplesUtils.getTestScenarioURL("holzkirchen"));
 
 		Set<String> modes = new HashSet<>();
@@ -99,6 +103,9 @@ void test() {
 		config.plans().setInputFile(plansFile);
 		config.network().setInputFile(networkFile);
 
+		DvrpTravelTimeMatrixParams matrixParams = dvrpConfigGroup.getTravelTimeMatrixParams();
+		matrixParams.addParameterSet(matrixParams.createParameterSet(SquareGridZoneSystemParams.SET_NAME));
+
 		config.qsim().setSimStarttimeInterpretation(QSimConfigGroup.StarttimeInterpretation.onlyUseStarttime);
 		config.qsim().setSimEndtimeInterpretation(QSimConfigGroup.EndtimeInterpretation.minOfEndtimeAndMobsimFinished);
 
diff --git a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/h3/RunDrtWithH3ZonalSystemIT.java b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/h3/RunDrtWithH3ZonalSystemIT.java
index 86595942ed2..d1ef4e2fea7 100644
--- a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/h3/RunDrtWithH3ZonalSystemIT.java
+++ b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/h3/RunDrtWithH3ZonalSystemIT.java
@@ -5,14 +5,16 @@
 import org.junit.jupiter.api.extension.RegisterExtension;
 import org.matsim.application.MATSimApplication;
 import org.matsim.contrib.common.zones.ZoneSystem;
+import org.matsim.contrib.common.zones.systems.grid.h3.H3GridZoneSystemParams;
 import org.matsim.contrib.drt.analysis.DrtEventSequenceCollector;
-import org.matsim.contrib.drt.analysis.zonal.DrtZonalSystemParams;
+import org.matsim.contrib.drt.analysis.zonal.DrtZoneSystemParams;
 import org.matsim.contrib.drt.analysis.zonal.DrtZonalWaitTimesAnalyzer;
 import org.matsim.contrib.drt.extension.DrtTestScenario;
 import org.matsim.contrib.drt.run.DrtConfigGroup;
 import org.matsim.contrib.drt.run.MultiModeDrtConfigGroup;
 import org.matsim.contrib.dvrp.run.AbstractDvrpModeModule;
 import org.matsim.core.config.Config;
+import org.matsim.core.config.ConfigGroup;
 import org.matsim.core.config.ConfigUtils;
 import org.matsim.core.controler.AbstractModule;
 import org.matsim.core.controler.Controler;
@@ -45,8 +47,12 @@ private static void prepare(Controler controler, Config config) {
 
 		MultiModeDrtConfigGroup drtConfigs = ConfigUtils.addOrGetModule(config, MultiModeDrtConfigGroup.class);
 		for (DrtConfigGroup drtConfig : drtConfigs.getModalElements()) {
-			drtConfig.getZonalSystemParams().get().h3Resolution = 9;
-			drtConfig.getZonalSystemParams().get().zonesGeneration = DrtZonalSystemParams.ZoneGeneration.H3;
+
+			DrtZoneSystemParams drtZoneSystemParams = drtConfig.getZonalSystemParams().get();
+			drtZoneSystemParams.removeParameterSet(drtZoneSystemParams.getZoneSystemParams());
+			ConfigGroup zoneSystemParams = drtZoneSystemParams.createParameterSet(H3GridZoneSystemParams.SET_NAME);
+			((H3GridZoneSystemParams) zoneSystemParams).h3Resolution = 9;
+			drtZoneSystemParams.addParameterSet(zoneSystemParams);
 			controler.addOverridingModule(new AbstractModule() {
 				@Override
 				public void install() {
@@ -67,11 +73,6 @@ public void install() {
 
 	private static void prepare(Config config) {
 		MultiModeDrtConfigGroup drtConfigs = ConfigUtils.addOrGetModule(config, MultiModeDrtConfigGroup.class);
-		for (DrtConfigGroup drtConfig : drtConfigs.getModalElements()) {
-			DrtZonalSystemParams params = drtConfig.getZonalSystemParams().orElseThrow();
-			params.cellSize = 1.;
-			params.zonesGeneration = DrtZonalSystemParams.ZoneGeneration.GridFromNetwork;
-		}
 	}
 
 	@Test
diff --git a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/h3/drtZone/H3DrtZonalSystemTest.java b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/h3/drtZone/H3DrtZonalSystemTest.java
index 293fa254280..3724d5d8eac 100644
--- a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/h3/drtZone/H3DrtZonalSystemTest.java
+++ b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/h3/drtZone/H3DrtZonalSystemTest.java
@@ -25,8 +25,7 @@
 import org.matsim.api.core.v01.network.Link;
 import org.matsim.api.core.v01.network.Network;
 import org.matsim.contrib.common.zones.ZoneSystem;
-import org.matsim.contrib.common.zones.h3.H3GridUtils;
-import org.matsim.contrib.common.zones.h3.H3ZoneSystemUtils;
+import org.matsim.contrib.common.zones.systems.grid.h3.H3ZoneSystem;
 import org.matsim.core.config.ConfigGroup;
 import org.matsim.core.network.NetworkUtils;
 import org.matsim.core.network.io.MatsimNetworkReader;
@@ -49,17 +48,16 @@ void test_Holzkirchen_Resolution3() {
 		Network network = getNetwork();
 		String crs = TransformationFactory.DHDN_GK4;
 		int resolution = 3;
-		ZoneSystem drtZonalSystem = H3ZoneSystemUtils.createFromPreparedGeometries(network,
-			H3GridUtils.createH3GridFromNetwork(network, resolution, crs), crs, resolution);
+		ZoneSystem drtZonalSystem = new H3ZoneSystem(crs, resolution, network, z -> true);
 
-		assertThat(drtZonalSystem.getZones().containsKey(createZoneId("831f8dfffffffff"))).isTrue();
+		assertThat(drtZonalSystem.getZones().containsKey(createZoneId("590526392240701439"))).isTrue();
 
 		// center of Holzkirchen
-		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(358598)).getId()).isEqualTo(createZoneId("831f8dfffffffff"));
+		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(358598)).orElseThrow().getId()).isEqualTo(createZoneId("590526667118608383"));
 		// Thanning (Western border of network)
-		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(78976)).getId()).isEqualTo(createZoneId("831f8dfffffffff"));
+		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(78976)).orElseThrow().getId()).isEqualTo(createZoneId("590526667118608383"));
 		// between Gross- and Kleinpienzenau (Southeastern border of network)
-		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(59914)).getId()).isEqualTo(createZoneId("831f89fffffffff"));
+		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(59914)).orElseThrow().getId()).isEqualTo(createZoneId("590526392240701439"));
 
 		//check all links are mapped
 		for (Link link : network.getLinks().values()) {
@@ -72,20 +70,20 @@ void test_Holzkirchen_Resolution5() {
 		Network network = getNetwork();
 		String crs = TransformationFactory.DHDN_GK4;
 		int resolution = 5;
-		ZoneSystem drtZonalSystem = H3ZoneSystemUtils.createFromPreparedGeometries(network,
-			H3GridUtils.createH3GridFromNetwork(network, resolution, crs), crs, resolution);
 
-		assertThat(drtZonalSystem.getZones().containsKey(createZoneId("851f88b7fffffff"))).isTrue();
-		assertThat(drtZonalSystem.getZones().containsKey(createZoneId("851f8d6bfffffff"))).isTrue();
-		assertThat(drtZonalSystem.getZones().containsKey(createZoneId("851f88a7fffffff"))).isTrue();
-		assertThat(drtZonalSystem.getZones().containsKey(createZoneId("851f89d3fffffff"))).isTrue();
+		ZoneSystem drtZonalSystem = new H3ZoneSystem(crs, resolution, network, z -> true);
+
+		assertThat(drtZonalSystem.getZones().containsKey(createZoneId("599533579684282367"))).isTrue();
+		assertThat(drtZonalSystem.getZones().containsKey(createZoneId("599533826644901887"))).isTrue();
+		assertThat(drtZonalSystem.getZones().containsKey(createZoneId("599533499153645567"))).isTrue();
+		assertThat(drtZonalSystem.getZones().containsKey(createZoneId("599533503448612863"))).isTrue();
 
 		// center of Holzkirchen
-		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(358598)).getId()).isEqualTo(createZoneId("851f8d6bfffffff"));
+		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(358598)).orElseThrow().getId()).isEqualTo(createZoneId("599533826644901887"));
 		// Thanning (Western border of network)
-		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(78976)).getId()).isEqualTo(createZoneId("851f88b7fffffff"));
+		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(78976)).orElseThrow().getId()).isEqualTo(createZoneId("599533503448612863"));
 		// between Gross- and Kleinpienzenau (Southeastern border of network)
-		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(59914)).getId()).isEqualTo(createZoneId("851f89d3fffffff"));
+		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(59914)).orElseThrow().getId()).isEqualTo(createZoneId("599533579684282367"));
 
 		//check all links are mapped
 		for (Link link : network.getLinks().values()) {
@@ -98,22 +96,15 @@ void test_Holzkirchen_Resolution6() {
 		Network network = getNetwork();
 		String crs = TransformationFactory.DHDN_GK4;
 		int resolution = 6;
-		ZoneSystem drtZonalSystem = H3ZoneSystemUtils.createFromPreparedGeometries(network,
-			H3GridUtils.createH3GridFromNetwork(network, resolution, crs), crs, resolution);
 
-		assertThat(drtZonalSystem.getZones().containsKey(createZoneId("861f8d697ffffff"))).isTrue();
-		assertThat(drtZonalSystem.getZones().containsKey(createZoneId("861f8d687ffffff"))).isTrue();
-		assertThat(drtZonalSystem.getZones().containsKey(createZoneId("861f8d69fffffff"))).isTrue();
-		assertThat(drtZonalSystem.getZones().containsKey(createZoneId("861f88a6fffffff"))).isTrue();
-		assertThat(drtZonalSystem.getZones().containsKey(createZoneId("861f88a6fffffff"))).isTrue();
-		assertThat(drtZonalSystem.getZones().containsKey(createZoneId("861f89d37ffffff"))).isTrue();
+		ZoneSystem drtZonalSystem = new H3ZoneSystem(crs, resolution, network, z -> true);
 
 		// center of Holzkirchen
-		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(358598)).getId()).isEqualTo(createZoneId("861f8d697ffffff"));
+		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(358598)).orElseThrow().getId()).isEqualTo(createZoneId("604037425601183743"));
 		// Thanning (Western border of network)
-		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(78976)).getId()).isEqualTo(createZoneId("861f88b47ffffff"));
+		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(78976)).orElseThrow().getId()).isEqualTo(createZoneId("604037102136459263"));
 		// between Gross- and Kleinpienzenau (Southeastern border of network)
-		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(59914)).getId()).isEqualTo(createZoneId("861f89d07ffffff"));
+		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(59914)).orElseThrow().getId()).isEqualTo(createZoneId("604037178372128767"));
 
 		//check all links are mapped
 		for (Link link : network.getLinks().values()) {
@@ -126,15 +117,14 @@ void test_Holzkirchen_Resolution10() {
 		Network network = getNetwork();
 		String crs = TransformationFactory.DHDN_GK4;
 		int resolution = 10;
-		ZoneSystem drtZonalSystem = H3ZoneSystemUtils.createFromPreparedGeometries(network,
-			H3GridUtils.createH3GridFromNetwork(network, resolution, crs), crs, resolution);
+		ZoneSystem drtZonalSystem = new H3ZoneSystem(crs, resolution, network, z -> true);
 
 		// center of Holzkirchen
-		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(358598)).getId()).isEqualTo(createZoneId("8a1f8d6930b7fff"));
+		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(358598)).orElseThrow().getId()).isEqualTo(createZoneId("622051824027533311"));
 		// Thanning (Western border of network)
-		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(78976)).getId()).isEqualTo(createZoneId("8a1f88b4025ffff"));
+		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(78976)).orElseThrow().getId()).isEqualTo(createZoneId("622051500514213887"));
 		// between Gross- and Kleinpienzenau (Southeastern border of network)
-		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(59914)).getId()).isEqualTo(createZoneId("8a1f89d06d5ffff"));
+		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(59914)).orElseThrow().getId()).isEqualTo(createZoneId("622051576862081023"));
 
 		//check all links are mapped
 		for (Link link : network.getLinks().values()) {
@@ -147,15 +137,16 @@ void test_Holzkirchen_Resolution12() {
 		Network network = getNetwork();
 		String crs = TransformationFactory.DHDN_GK4;
 		int resolution = 12;
-		ZoneSystem drtZonalSystem = H3ZoneSystemUtils.createFromPreparedGeometries(network,
-			H3GridUtils.createH3GridFromNetwork(network, resolution, crs), crs, resolution);
+
+		ZoneSystem drtZonalSystem = new H3ZoneSystem(crs, resolution, network, z -> true);
+
 
 		// center of Holzkirchen
-		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(358598)).getId()).isEqualTo(createZoneId("8c1f8d6930b63ff"));
+		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(358598)).orElseThrow().getId()).isEqualTo(createZoneId("631059023282267135"));
 		// Thanning (Western border of network)
-		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(78976)).getId()).isEqualTo(createZoneId("8c1f88b4025d1ff"));
+		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(78976)).orElseThrow().getId()).isEqualTo(createZoneId("631058699768943103"));
 		// between Gross- and Kleinpienzenau (Southeastern border of network)
-		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(59914)).getId()).isEqualTo(createZoneId("8c1f89d06d581ff"));
+		assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId(59914)).orElseThrow().getId()).isEqualTo(createZoneId("631058776116789759"));
 
 		//check all links are mapped
 		for (Link link : network.getLinks().values()) {
diff --git a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/insertion/DrtInsertionExtensionIT.java b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/insertion/DrtInsertionExtensionIT.java
index be42758f698..15ef8a6548b 100644
--- a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/insertion/DrtInsertionExtensionIT.java
+++ b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/insertion/DrtInsertionExtensionIT.java
@@ -18,6 +18,7 @@
 import org.matsim.api.core.v01.events.handler.LinkEnterEventHandler;
 import org.matsim.api.core.v01.network.Link;
 import org.matsim.api.core.v01.network.Network;
+import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystemParams;
 import org.matsim.contrib.drt.extension.insertion.distances.DistanceApproximator;
 import org.matsim.contrib.drt.run.DrtConfigGroup;
 import org.matsim.contrib.drt.run.DrtControlerCreator;
@@ -39,7 +40,9 @@
 import org.matsim.contrib.dvrp.run.DvrpConfigGroup;
 import org.matsim.contrib.dvrp.vrpagent.TaskEndedEvent;
 import org.matsim.contrib.dvrp.vrpagent.TaskEndedEventHandler;
+import org.matsim.contrib.zone.skims.DvrpTravelTimeMatrixParams;
 import org.matsim.core.config.Config;
+import org.matsim.core.config.ConfigGroup;
 import org.matsim.core.config.ConfigUtils;
 import org.matsim.core.controler.AbstractModule;
 import org.matsim.core.controler.Controler;
@@ -58,9 +61,14 @@ public class DrtInsertionExtensionIT {
 	private Controler createController() {
 		URL configUrl = IOUtils.extendUrl(ExamplesUtils.getTestScenarioURL("mielec"), "mielec_drt_config.xml");
 
-		Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), new DvrpConfigGroup(),
+		DvrpConfigGroup dvrpConfigGroup = new DvrpConfigGroup();
+		Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), dvrpConfigGroup,
 				new OTFVisConfigGroup());
 
+		DvrpTravelTimeMatrixParams matrixParams = dvrpConfigGroup.getTravelTimeMatrixParams();
+		ConfigGroup zoneSystemParams = matrixParams.createParameterSet(SquareGridZoneSystemParams.SET_NAME);
+		matrixParams.addParameterSet(zoneSystemParams);
+
 		config.controller().setOutputDirectory(utils.getOutputDirectory());
 		config.controller().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists);
 
@@ -283,7 +291,7 @@ void testRangeConstraintWithCustomInstances() {
 		}
 
 		assertEquals(1470, distanceCalculator.calculatedDistances);
-		assertEquals(5280, distanceApproximator.calculatedDistances);
+		assertEquals(5286, distanceApproximator.calculatedDistances);
 	}
 
 	@Test
@@ -319,7 +327,7 @@ protected void configureQSim() {
 		}
 
 		assertEquals(1470, distanceCalculator.calculatedDistances);
-		assertEquals(5280, distanceApproximator.calculatedDistances);
+		assertEquals(5286, distanceApproximator.calculatedDistances);
 	}
 
 	static class CustomDistanceCalculator extends CustomCalculator {
@@ -421,10 +429,10 @@ void testDefaults() {
 
 		controller.run();
 
-		assertEquals(16, handler.rejectedRequests);
-		assertEquals(2112862.0, handler.fleetDistance, 1e-3);
-		assertEquals(698710.0, handler.activeTime(), 1e-3);
-		assertEquals(280.19623, handler.meanWaitTime(), 1e-3);
+		assertEquals(18, handler.rejectedRequests);
+		assertEquals(2097060.0, handler.fleetDistance, 1e-3);
+		assertEquals(700441.0, handler.activeTime(), 1e-3);
+		assertEquals(278.5162162162162, handler.meanWaitTime(), 1e-3);
 	}
 
 	@Test
@@ -442,10 +450,10 @@ void testVehicleActiveTimeObjective() {
 
 		controller.run();
 
-		assertEquals(16, handler.rejectedRequests);
-		assertEquals(2112862.0, handler.fleetDistance, 1e-3);
-		assertEquals(698710.0, handler.activeTime(), 1e-3);
-		assertEquals(280.19623, handler.meanWaitTime(), 1e-3);
+		assertEquals(18, handler.rejectedRequests);
+		assertEquals(2097060, handler.fleetDistance, 1e-3);
+		assertEquals(700441.0, handler.activeTime(), 1e-3);
+		assertEquals(278.5162162162162, handler.meanWaitTime(), 1e-3);
 	}
 
 	@Test
diff --git a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/operations/eshifts/run/RunEShiftDrtScenarioIT.java b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/operations/eshifts/run/RunEShiftDrtScenarioIT.java
index 05e08557722..2bd5fdffc27 100644
--- a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/operations/eshifts/run/RunEShiftDrtScenarioIT.java
+++ b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/operations/eshifts/run/RunEShiftDrtScenarioIT.java
@@ -2,7 +2,8 @@
 
 import org.junit.jupiter.api.Test;
 import org.matsim.api.core.v01.TransportMode;
-import org.matsim.contrib.drt.analysis.zonal.DrtZonalSystemParams;
+import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystemParams;
+import org.matsim.contrib.drt.analysis.zonal.DrtZoneSystemParams;
 import org.matsim.contrib.drt.extension.operations.DrtOperationsParams;
 import org.matsim.contrib.drt.extension.operations.DrtWithOperationsConfigGroup;
 import org.matsim.contrib.drt.extension.operations.EDrtOperationsControlerCreator;
@@ -17,6 +18,7 @@
 import org.matsim.contrib.ev.EvConfigGroup;
 import org.matsim.contrib.ev.charging.*;
 import org.matsim.contrib.ev.temperature.TemperatureService;
+import org.matsim.contrib.zone.skims.DvrpTravelTimeMatrixParams;
 import org.matsim.core.config.Config;
 import org.matsim.core.config.ConfigGroup;
 import org.matsim.core.config.ConfigUtils;
@@ -78,16 +80,21 @@ void test() {
 
 		drtConfigGroup.getRebalancingParams().get().addParameterSet(strategyParams);
 
-		DrtZonalSystemParams drtZonalSystemParams = new DrtZonalSystemParams();
-		drtZonalSystemParams.zonesGeneration = DrtZonalSystemParams.ZoneGeneration.GridFromNetwork;
-		drtZonalSystemParams.cellSize = 500.;
-		drtZonalSystemParams.targetLinkSelection = DrtZonalSystemParams.TargetLinkSelection.mostCentral;
-		drtConfigGroup.addParameterSet(drtZonalSystemParams);
+		DrtZoneSystemParams drtZoneSystemParams = new DrtZoneSystemParams();
+		ConfigGroup parameterSet = drtZoneSystemParams.createParameterSet(SquareGridZoneSystemParams.SET_NAME);
+		((SquareGridZoneSystemParams) parameterSet).cellSize = 500.;
+		drtZoneSystemParams.addParameterSet(parameterSet);
+		drtZoneSystemParams.targetLinkSelection = DrtZoneSystemParams.TargetLinkSelection.mostCentral;
+		drtConfigGroup.addParameterSet(drtZoneSystemParams);
 
 		multiModeDrtConfigGroup.addParameterSet(drtWithShiftsConfigGroup);
 
+		DvrpConfigGroup dvrpConfigGroup = new DvrpConfigGroup();
+		DvrpTravelTimeMatrixParams matrixParams = dvrpConfigGroup.getTravelTimeMatrixParams();
+		matrixParams.addParameterSet(matrixParams.createParameterSet(SquareGridZoneSystemParams.SET_NAME));
+
 		final Config config = ConfigUtils.createConfig(multiModeDrtConfigGroup,
-				new DvrpConfigGroup());
+			dvrpConfigGroup);
 		config.setContext(ExamplesUtils.getTestScenarioURL("holzkirchen"));
 
 		Set<String> modes = new HashSet<>();
diff --git a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/operations/shifts/run/RunMultiHubShiftDrtScenarioIT.java b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/operations/shifts/run/RunMultiHubShiftDrtScenarioIT.java
index 5b3f79ea7dd..b0b5089b81e 100644
--- a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/operations/shifts/run/RunMultiHubShiftDrtScenarioIT.java
+++ b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/operations/shifts/run/RunMultiHubShiftDrtScenarioIT.java
@@ -2,7 +2,8 @@
 
 import org.junit.jupiter.api.Test;
 import org.matsim.api.core.v01.TransportMode;
-import org.matsim.contrib.drt.analysis.zonal.DrtZonalSystemParams;
+import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystemParams;
+import org.matsim.contrib.drt.analysis.zonal.DrtZoneSystemParams;
 import org.matsim.contrib.drt.extension.operations.DrtOperationsControlerCreator;
 import org.matsim.contrib.drt.extension.operations.DrtWithOperationsConfigGroup;
 import org.matsim.contrib.drt.extension.operations.DrtOperationsParams;
@@ -14,6 +15,7 @@
 import org.matsim.contrib.drt.run.DrtConfigGroup;
 import org.matsim.contrib.drt.run.MultiModeDrtConfigGroup;
 import org.matsim.contrib.dvrp.run.DvrpConfigGroup;
+import org.matsim.contrib.zone.skims.DvrpTravelTimeMatrixParams;
 import org.matsim.core.config.Config;
 import org.matsim.core.config.ConfigGroup;
 import org.matsim.core.config.ConfigUtils;
@@ -67,16 +69,21 @@ void test() {
 
 		drtConfigGroup.getRebalancingParams().get().addParameterSet(strategyParams);
 
-		DrtZonalSystemParams drtZonalSystemParams = new DrtZonalSystemParams();
-		drtZonalSystemParams.zonesGeneration = DrtZonalSystemParams.ZoneGeneration.GridFromNetwork;
-		drtZonalSystemParams.cellSize = 500.;
-		drtZonalSystemParams.targetLinkSelection = DrtZonalSystemParams.TargetLinkSelection.mostCentral;
-		drtConfigGroup.addParameterSet(drtZonalSystemParams);
+		DrtZoneSystemParams drtZoneSystemParams = new DrtZoneSystemParams();
+		SquareGridZoneSystemParams zoneParams = (SquareGridZoneSystemParams) drtZoneSystemParams.createParameterSet(SquareGridZoneSystemParams.SET_NAME);
+		zoneParams.cellSize = 500.;
+		drtZoneSystemParams.addParameterSet(zoneParams);
+		drtZoneSystemParams.targetLinkSelection = DrtZoneSystemParams.TargetLinkSelection.mostCentral;
+		drtConfigGroup.addParameterSet(drtZoneSystemParams);
 
 		multiModeDrtConfigGroup.addParameterSet(drtWithShiftsConfigGroup);
 
+		DvrpConfigGroup dvrpConfigGroup = new DvrpConfigGroup();
+		DvrpTravelTimeMatrixParams matrixParams = dvrpConfigGroup.getTravelTimeMatrixParams();
+		matrixParams.addParameterSet(matrixParams.createParameterSet(SquareGridZoneSystemParams.SET_NAME));
+
 		final Config config = ConfigUtils.createConfig(multiModeDrtConfigGroup,
-				new DvrpConfigGroup());
+			dvrpConfigGroup);
 		config.setContext(ExamplesUtils.getTestScenarioURL("holzkirchen"));
 
 		Set<String> modes = new HashSet<>();
diff --git a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/operations/shifts/run/RunShiftDrtScenarioIT.java b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/operations/shifts/run/RunShiftDrtScenarioIT.java
index 28034b937c6..1d85910ebec 100644
--- a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/operations/shifts/run/RunShiftDrtScenarioIT.java
+++ b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/operations/shifts/run/RunShiftDrtScenarioIT.java
@@ -2,7 +2,8 @@
 
 import org.junit.jupiter.api.Test;
 import org.matsim.api.core.v01.TransportMode;
-import org.matsim.contrib.drt.analysis.zonal.DrtZonalSystemParams;
+import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystemParams;
+import org.matsim.contrib.drt.analysis.zonal.DrtZoneSystemParams;
 import org.matsim.contrib.drt.extension.operations.DrtOperationsControlerCreator;
 import org.matsim.contrib.drt.extension.operations.DrtOperationsParams;
 import org.matsim.contrib.drt.extension.operations.DrtWithOperationsConfigGroup;
@@ -14,6 +15,7 @@
 import org.matsim.contrib.drt.run.DrtConfigGroup;
 import org.matsim.contrib.drt.run.MultiModeDrtConfigGroup;
 import org.matsim.contrib.dvrp.run.DvrpConfigGroup;
+import org.matsim.contrib.zone.skims.DvrpTravelTimeMatrixParams;
 import org.matsim.core.config.Config;
 import org.matsim.core.config.ConfigGroup;
 import org.matsim.core.config.ConfigUtils;
@@ -68,16 +70,21 @@ void test() {
 
 		drtConfigGroup.getRebalancingParams().get().addParameterSet(strategyParams);
 
-		DrtZonalSystemParams drtZonalSystemParams = new DrtZonalSystemParams();
-		drtZonalSystemParams.zonesGeneration = DrtZonalSystemParams.ZoneGeneration.GridFromNetwork;
-		drtZonalSystemParams.cellSize = 500.;
-		drtZonalSystemParams.targetLinkSelection = DrtZonalSystemParams.TargetLinkSelection.mostCentral;
-		drtConfigGroup.addParameterSet(drtZonalSystemParams);
+		DrtZoneSystemParams drtZoneSystemParams = new DrtZoneSystemParams();
+		SquareGridZoneSystemParams zoneParams = (SquareGridZoneSystemParams) drtZoneSystemParams.createParameterSet(SquareGridZoneSystemParams.SET_NAME);
+		zoneParams.cellSize = 500.;
+		drtZoneSystemParams.addParameterSet(zoneParams);
+		drtZoneSystemParams.targetLinkSelection = DrtZoneSystemParams.TargetLinkSelection.mostCentral;
+		drtConfigGroup.addParameterSet(drtZoneSystemParams);
 
 		multiModeDrtConfigGroup.addParameterSet(drtWithShiftsConfigGroup);
 
+		DvrpConfigGroup dvrpConfigGroup = new DvrpConfigGroup();
+		DvrpTravelTimeMatrixParams matrixParams = dvrpConfigGroup.getTravelTimeMatrixParams();
+		matrixParams.addParameterSet(matrixParams.createParameterSet(SquareGridZoneSystemParams.SET_NAME));
+
 		final Config config = ConfigUtils.createConfig(multiModeDrtConfigGroup,
-				new DvrpConfigGroup());
+			dvrpConfigGroup);
 		config.setContext(ExamplesUtils.getTestScenarioURL("holzkirchen"));
 
 		Set<String> modes = new HashSet<>();
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtGridUtils.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtGridUtils.java
deleted file mode 100644
index 75101e4bc6c..00000000000
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtGridUtils.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/* *********************************************************************** *
- * project: org.matsim.*
- *                                                                         *
- * *********************************************************************** *
- *                                                                         *
- * copyright       : (C) 2017 by the members listed in the COPYING,        *
- *                   LICENSE and WARRANTY file.                            *
- * email           : info at matsim dot org                                *
- *                                                                         *
- * *********************************************************************** *
- *                                                                         *
- *   This program is free software; you can redistribute it and/or modify  *
- *   it under the terms of the GNU General Public License as published by  *
- *   the Free Software Foundation; either version 2 of the License, or     *
- *   (at your option) any later version.                                   *
- *   See also COPYING, LICENSE and WARRANTY file                           *
- *                                                                         *
- * *********************************************************************** */
-
-/**
- *
- */
-package org.matsim.contrib.drt.analysis.zonal;
-
-import one.util.streamex.EntryStream;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.GeometryFactory;
-import org.locationtech.jts.geom.Polygon;
-import org.locationtech.jts.geom.prep.PreparedGeometry;
-import org.locationtech.jts.geom.prep.PreparedGeometryFactory;
-import org.matsim.api.core.v01.network.Network;
-import org.matsim.core.network.NetworkUtils;
-import org.matsim.core.utils.misc.Counter;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * @author jbischoff
- */
-public class DrtGridUtils {
-
-	static final Logger log = LogManager.getLogger(DrtGridUtils.class);
-
-	public static Map<String, PreparedGeometry> createGridFromNetwork(Network network, double cellsize) {
-		log.info("start creating grid from network");
-		double[] boundingbox = NetworkUtils.getBoundingBox(network.getNodes().values());
-		double minX = (Math.floor(boundingbox[0] / cellsize) * cellsize);
-		double maxX = (Math.ceil(boundingbox[2] / cellsize) * cellsize);
-		double minY = (Math.floor(boundingbox[1] / cellsize) * cellsize);
-		double maxY = (Math.ceil(boundingbox[3] / cellsize) * cellsize);
-		GeometryFactory gf = new GeometryFactory();
-		PreparedGeometryFactory preparedGeometryFactory = new PreparedGeometryFactory();
-		Map<String, PreparedGeometry> grid = new HashMap<>();
-		int cell = 0;
-		for (double lx = minX; lx < maxX; lx += cellsize) {
-			for (double by = minY; by < maxY; by += cellsize) {
-				cell++;
-				Coordinate p1 = new Coordinate(lx, by);
-				Coordinate p2 = new Coordinate(lx + cellsize, by);
-				Coordinate p3 = new Coordinate(lx + cellsize, by + cellsize);
-				Coordinate p4 = new Coordinate(lx, by + cellsize);
-				Coordinate[] ca = { p1, p2, p3, p4, p1 };
-				Polygon polygon = new Polygon(gf.createLinearRing(ca), null, gf);
-				grid.put(cell + "", preparedGeometryFactory.create(polygon));
-			}
-		}
-		log.info("finished creating grid from network");
-		return grid;
-	}
-
-	/**
-	 * Takes an existing grid and removes all zones that do not intersect the service area.
-	 * Result may contain zones that are barely included in the service area. But as passengers may walk into the service area,
-	 * it seems appropriate that the DrtZonalSystem, which is used for demand estimation, is larger than the service area.
-	 * The {@code cellsize} indirectly determines, how much larger the DrtZonalSystem may get.
-	 *
-	 * @param grid a pre-computed grid of zones
-	 * @param serviceAreaGeoms geometries that define the service area
-	 * @return
-	 */
-	public static Map<String, PreparedGeometry> filterGridWithinServiceArea(Map<String, PreparedGeometry> grid, List<PreparedGeometry> serviceAreaGeoms) {
-		log.info("total number of initial grid zones = " + grid.size());
-		log.info("searching for grid zones within the drt service area...");
-		Counter counter = new Counter("dealt with zone ");
-		Map<String, PreparedGeometry> zonesWithinServiceArea = EntryStream.of(grid)
-				.peekKeys(id -> counter.incCounter())
-				.filterValues(cell -> serviceAreaGeoms.stream()
-						.anyMatch(serviceArea -> serviceArea.intersects(cell.getGeometry())))
-				.toMap();
-
-		log.info("number of remaining grid zones = " + zonesWithinServiceArea.size());
-		return zonesWithinServiceArea;
-	}
-}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtModeZonalSystemModule.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtModeZonalSystemModule.java
index bd3e6b7ddc6..52f76e5b8db 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtModeZonalSystemModule.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtModeZonalSystemModule.java
@@ -23,11 +23,17 @@
 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.h3.H3GridUtils;
-import org.matsim.contrib.common.zones.h3.H3ZoneSystemUtils;
+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;
@@ -35,11 +41,10 @@
 import org.matsim.core.controler.MatsimServices;
 
 import java.util.List;
-import java.util.Map;
+import java.util.function.Predicate;
 
-import static org.matsim.contrib.drt.analysis.zonal.DrtGridUtils.createGridFromNetwork;
-import static org.matsim.contrib.drt.analysis.zonal.DrtGridUtils.filterGridWithinServiceArea;
 import static org.matsim.utils.gis.shp2matsim.ShpGeometryUtils.loadPreparedGeometries;
+import static org.matsim.utils.gis.shp2matsim.ShpGeometryUtils.loadPreparedPolygons;
 
 /**
  * @author Michal Maciejewski (michalm)
@@ -56,43 +61,51 @@ public DrtModeZonalSystemModule(DrtConfigGroup drtCfg) {
 	@Override
 	public void install() {
 		if (drtCfg.getZonalSystemParams().isPresent()) {
-			DrtZonalSystemParams params = drtCfg.getZonalSystemParams().get();
+			DrtZoneSystemParams params = drtCfg.getZonalSystemParams().get();
+			ZoneSystemParams zoneSystemParams = params.getZoneSystemParams();
 
 			bindModal(ZoneSystem.class).toProvider(modalProvider(getter -> {
 				Network network = getter.getModal(Network.class);
-				switch (params.zonesGeneration) {
-					case ShapeFile: {
-						final List<PreparedGeometry> preparedGeometries = loadPreparedGeometries(
-							ConfigGroup.getInputFileURL(getConfig().getContext(), params.zonesShapeFile));
+				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 GridFromNetwork: {
-						Preconditions.checkNotNull(params.cellSize);
-						Map<String, PreparedGeometry> gridFromNetwork = createGridFromNetwork(network, params.cellSize);
-						var gridZones =
-							switch (drtCfg.operationalScheme) {
-								case stopbased, door2door -> gridFromNetwork;
-								case serviceAreaBased -> filterGridWithinServiceArea(gridFromNetwork,
-									loadPreparedGeometries(ConfigGroup.getInputFileURL(getConfig().getContext(),
-										drtCfg.drtServiceAreaShapeFile)));
-							};
-						return ZoneSystemUtils.createFromPreparedGeometries(network, gridZones);
+					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 H3:
-						Preconditions.checkNotNull(params.h3Resolution);
+					case H3GridZoneSystemParams.SET_NAME: {
+
+						Preconditions.checkNotNull(((H3GridZoneSystemParams) zoneSystemParams).h3Resolution);
 						String crs = getConfig().global().getCoordinateSystem();
-						Map<String, PreparedGeometry> gridFromNetwork = H3GridUtils.createH3GridFromNetwork(network, params.h3Resolution, crs);
-						var gridZones =
-							switch (drtCfg.operationalScheme) {
-								case stopbased, door2door -> gridFromNetwork;
-								case serviceAreaBased -> filterGridWithinServiceArea(gridFromNetwork,
-									loadPreparedGeometries(ConfigGroup.getInputFileURL(getConfig().getContext(),
-										drtCfg.drtServiceAreaShapeFile)));
-							};
-						return H3ZoneSystemUtils.createFromPreparedGeometries(network, gridZones, crs, params.h3Resolution);
+
+						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");
@@ -104,7 +117,7 @@ public void install() {
 					case mostCentral:
 						return new MostCentralDrtZoneTargetLinkSelector(getter.getModal(ZoneSystem.class));
 					case random:
-						return new RandomDrtZoneTargetLinkSelector();
+						return new RandomDrtZoneTargetLinkSelector(getter.getModal(ZoneSystem.class));
 					default:
 						throw new RuntimeException(
 							"Unsupported target link selection = " + params.targetLinkSelection);
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtZonalSystemParams.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtZonalSystemParams.java
deleted file mode 100644
index 4dd0860b578..00000000000
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtZonalSystemParams.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * *********************************************************************** *
- * project: org.matsim.*
- * *********************************************************************** *
- *                                                                         *
- * copyright       : (C) 2020 by the members listed in the COPYING,        *
- *                   LICENSE and WARRANTY file.                            *
- * email           : info at matsim dot org                                *
- *                                                                         *
- * *********************************************************************** *
- *                                                                         *
- *   This program is free software; you can redistribute it and/or modify  *
- *   it under the terms of the GNU General Public License as published by  *
- *   the Free Software Foundation; either version 2 of the License, or     *
- *   (at your option) any later version.                                   *
- *   See also COPYING, LICENSE and WARRANTY file                           *
- *                                                                         *
- * *********************************************************************** *
- */
-
-package org.matsim.contrib.drt.analysis.zonal;
-
-import javax.annotation.Nullable;
-
-import org.matsim.contrib.common.zones.h3.H3Utils;
-import org.matsim.core.config.Config;
-import org.matsim.core.config.ReflectiveConfigGroup;
-
-import com.google.common.base.Preconditions;
-
-import jakarta.validation.constraints.NotNull;
-import jakarta.validation.constraints.Positive;
-
-/**
- * @author Michal Maciejewski (michalm)
- */
-public class DrtZonalSystemParams extends ReflectiveConfigGroup {
-	public static final String SET_NAME = "zonalSystem";
-
-	public DrtZonalSystemParams() {
-		super(SET_NAME);
-	}
-
-	public enum ZoneGeneration {GridFromNetwork, ShapeFile, H3}
-
-	@Parameter
-	@Comment("Logic for generation of zones for the DRT zonal system. Value can be: [GridFromNetwork, ShapeFile, H3].")
-	@NotNull
-	public ZoneGeneration zonesGeneration = null;
-
-	@Parameter
-	@Comment("size of square cells used for demand aggregation."
-			+ " Depends on demand, supply and network. Often used with values in the range of 500 - 2000 m")
-	@Nullable
-	@Positive
-	public Double cellSize = null;// [m]
-
-	@Parameter
-	@Comment("allows to configure zones. Used with zonesGeneration=ShapeFile")
-	@Nullable
-	public String zonesShapeFile = null;
-
-	@Parameter
-	@Comment("allows to configure H3 hexagonal zones. Used with zonesGeneration=H3. " +
-		"Range from 0 (122 cells worldwide) to 15 (569 E^12 cells). " +
-		"Usually meaningful between resolution 6 (3.7 km avg edge length) " +
-		"and 10 (70 m avg edge length). ")
-	@Nullable
-	public Integer h3Resolution = null;
-
-	public enum TargetLinkSelection {random, mostCentral}
-
-	@Parameter("zoneTargetLinkSelection")
-	@Comment("Defines how the target link of a zone is determined (e.g. for rebalancing)."
-			+ " Possible values are [random,mostCentral]. Default behavior is mostCentral, where all vehicles are sent to the same link.")
-	@NotNull
-	public TargetLinkSelection targetLinkSelection = TargetLinkSelection.mostCentral;
-
-	@Override
-	protected void checkConsistency(Config config) {
-		super.checkConsistency(config);
-
-		Preconditions.checkArgument(zonesGeneration != ZoneGeneration.GridFromNetwork || cellSize != null,
-				"cellSize must not be null when zonesGeneration is " + ZoneGeneration.GridFromNetwork);
-		Preconditions.checkArgument(zonesGeneration != ZoneGeneration.ShapeFile || zonesShapeFile != null,
-				"zonesShapeFile must not be null when zonesGeneration is " + ZoneGeneration.ShapeFile);
-		Preconditions.checkArgument(zonesGeneration != ZoneGeneration.H3 || h3Resolution != null,
-				"H3 resolution must not be null when zonesGeneration is " + ZoneGeneration.H3);
-		Preconditions.checkArgument(h3Resolution == null || h3Resolution >= 0 && h3Resolution <= H3Utils.MAX_RES,
-				"H3 resolution must not be null when zonesGeneration is " + ZoneGeneration.H3);
-	}
-}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtZonalWaitTimesAnalyzer.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtZonalWaitTimesAnalyzer.java
index 4972db0a280..f86d7759e37 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtZonalWaitTimesAnalyzer.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtZonalWaitTimesAnalyzer.java
@@ -29,6 +29,7 @@
 import org.locationtech.jts.geom.Polygon;
 import org.matsim.api.core.v01.Id;
 import org.matsim.api.core.v01.IdMap;
+import org.matsim.api.core.v01.Identifiable;
 import org.matsim.api.core.v01.population.Person;
 import org.matsim.contrib.common.zones.Zone;
 import org.matsim.contrib.common.zones.ZoneSystem;
@@ -139,10 +140,10 @@ private Map<Id<Zone>, DescriptiveStatistics> createZonalStats() {
 		for (EventSequence seq : requestAnalyzer.getPerformedRequestSequences().values()) {
 			for (Map.Entry<Id<Person>, EventSequence.PersonEvents> entry : seq.getPersonEvents().entrySet()) {
 				if(entry.getValue().getPickedUp().isPresent()) {
-					Zone zone = zones.getZoneForLinkId(seq.getSubmitted().getFromLinkId());
-					final Id<Zone> zoneStr = zone != null ? zone.getId() : zoneIdForOutsideOfZonalSystem;
+					Id<Zone> zone = zones.getZoneForLinkId(seq.getSubmitted().getFromLinkId())
+						.map(Identifiable::getId).orElse(zoneIdForOutsideOfZonalSystem);
 					double waitTime = entry.getValue().getPickedUp().get() .getTime() - seq.getSubmitted().getTime();
-					zoneStats.get(zoneStr).addValue(waitTime);
+					zoneStats.get(zone).addValue(waitTime);
 				}
 			}
 		}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtZoneSystemParams.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtZoneSystemParams.java
new file mode 100644
index 00000000000..0f9ed49ea1f
--- /dev/null
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtZoneSystemParams.java
@@ -0,0 +1,72 @@
+/*
+ * *********************************************************************** *
+ * project: org.matsim.*
+ * *********************************************************************** *
+ *                                                                         *
+ * copyright       : (C) 2020 by the members listed in the COPYING,        *
+ *                   LICENSE and WARRANTY file.                            *
+ * email           : info at matsim dot org                                *
+ *                                                                         *
+ * *********************************************************************** *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *   See also COPYING, LICENSE and WARRANTY file                           *
+ *                                                                         *
+ * *********************************************************************** *
+ */
+
+package org.matsim.contrib.drt.analysis.zonal;
+
+import jakarta.validation.constraints.NotNull;
+import org.matsim.contrib.common.util.ReflectiveConfigGroupWithConfigurableParameterSets;
+import org.matsim.contrib.common.zones.ZoneSystemParams;
+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.square.SquareGridZoneSystemParams;
+
+/**
+ * @author Michal Maciejewski (michalm)
+ */
+public class DrtZoneSystemParams extends ReflectiveConfigGroupWithConfigurableParameterSets {
+	public static final String SET_NAME = "zonalSystem";
+
+	public DrtZoneSystemParams() {
+		super(SET_NAME);
+		initSingletonParameterSets();
+	}
+
+	public enum TargetLinkSelection {random, mostCentral}
+
+	@Parameter("zoneTargetLinkSelection")
+	@Comment("Defines how the target link of a zone is determined (e.g. for rebalancing)."
+			+ " Possible values are [random,mostCentral]. Default behavior is mostCentral, where all vehicles are sent to the same link.")
+	@NotNull
+	public TargetLinkSelection targetLinkSelection = TargetLinkSelection.mostCentral;
+
+	private ZoneSystemParams zoneSystemParams;
+
+
+	private void initSingletonParameterSets() {
+
+		//insertion search params (one of: extensive, selective, repeated selective)
+		addDefinition(SquareGridZoneSystemParams.SET_NAME, SquareGridZoneSystemParams::new,
+			() -> zoneSystemParams,
+			params -> zoneSystemParams = (SquareGridZoneSystemParams)params);
+
+		addDefinition(GISFileZoneSystemParams.SET_NAME, GISFileZoneSystemParams::new,
+			() -> zoneSystemParams,
+			params -> zoneSystemParams = (GISFileZoneSystemParams)params);
+
+		addDefinition(H3GridZoneSystemParams.SET_NAME, H3GridZoneSystemParams::new,
+			() -> zoneSystemParams,
+			params -> zoneSystemParams = (H3GridZoneSystemParams)params);
+	}
+
+	public ZoneSystemParams getZoneSystemParams() {
+		return zoneSystemParams;
+	}
+
+}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/MostCentralDrtZoneTargetLinkSelector.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/MostCentralDrtZoneTargetLinkSelector.java
index 22022631dcc..9b24b56d5d2 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/MostCentralDrtZoneTargetLinkSelector.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/MostCentralDrtZoneTargetLinkSelector.java
@@ -37,11 +37,11 @@
 public class MostCentralDrtZoneTargetLinkSelector implements DrtZoneTargetLinkSelector {
 	private final Map<Zone, Link> targetLinks;
 
-	public MostCentralDrtZoneTargetLinkSelector(ZoneSystem drtZonalSystem) {
-		targetLinks = drtZonalSystem.getZones()
+	public MostCentralDrtZoneTargetLinkSelector(ZoneSystem zoneSystem) {
+		targetLinks = zoneSystem.getZones()
 				.values()
 				.stream()
-				.collect(toMap(zone -> zone, zone -> zone.getLinks().stream().min(
+				.collect(toMap(zone -> zone, zone -> zoneSystem.getLinksForZoneId(zone.getId()).stream().min(
 						//1. choose links with the most central toNode (there may be several "most central" nodes)
 						//2. if there is more than one such link (which is usually the case),
 						//   choose one with the most central fromNode
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/RandomDrtZoneTargetLinkSelector.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/RandomDrtZoneTargetLinkSelector.java
index 7c7f4413e0a..c1edf0e4f0e 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/RandomDrtZoneTargetLinkSelector.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/RandomDrtZoneTargetLinkSelector.java
@@ -20,10 +20,12 @@
 
 package org.matsim.contrib.drt.analysis.zonal;
 
+import java.util.List;
 import java.util.function.IntUnaryOperator;
 
 import org.matsim.api.core.v01.network.Link;
 import org.matsim.contrib.common.zones.Zone;
+import org.matsim.contrib.common.zones.ZoneSystem;
 import org.matsim.core.gbl.MatsimRandom;
 
 /**
@@ -31,17 +33,20 @@
  */
 public class RandomDrtZoneTargetLinkSelector implements DrtZoneTargetLinkSelector {
 	private final IntUnaryOperator random;
+	private final ZoneSystem zoneSystem;
 
-	public RandomDrtZoneTargetLinkSelector() {
-		this(MatsimRandom.getLocalInstance()::nextInt);
+	public RandomDrtZoneTargetLinkSelector(ZoneSystem zoneSystem) {
+		this(zoneSystem, MatsimRandom.getLocalInstance()::nextInt);
 	}
 
-	public RandomDrtZoneTargetLinkSelector(IntUnaryOperator random) {
+	public RandomDrtZoneTargetLinkSelector(ZoneSystem zoneSystem, IntUnaryOperator random) {
+		this.zoneSystem = zoneSystem;
 		this.random = random;
 	}
 
 	@Override
 	public Link selectTargetLink(Zone zone) {
-		return zone.getLinks().get(random.applyAsInt(zone.getLinks().size()));
+		List<Link> linksForZone = zoneSystem.getLinksForZoneId(zone.getId());
+		return linksForZone.get(random.applyAsInt(linksForZone.size()));
 	}
 }
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/ZonalIdleVehicleCollector.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/ZonalIdleVehicleCollector.java
index 2626e21fc3f..2b965ce7f6b 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/ZonalIdleVehicleCollector.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/ZonalIdleVehicleCollector.java
@@ -64,10 +64,7 @@ public void handleEvent(TaskEndedEvent event) {
 
 	private void handleEvent(AbstractTaskEvent event, Consumer<Zone> handler) {
 		if (event.getDvrpMode().equals(dvrpMode) && event.getTaskType().equals(DrtStayTask.TYPE)) {
-			Zone zone = zonalSystem.getZoneForLinkId(event.getLinkId());
-			if (zone != null) {
-				handler.accept(zone);
-			}
+			zonalSystem.getZoneForLinkId(event.getLinkId()).ifPresent(handler);
 		}
 	}
 
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/ZonalIdleVehicleXYVisualiser.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/ZonalIdleVehicleXYVisualiser.java
index 8813f7673ab..4d00795c440 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/ZonalIdleVehicleXYVisualiser.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/ZonalIdleVehicleXYVisualiser.java
@@ -83,10 +83,7 @@ public void handleEvent(TaskEndedEvent event) {
 
 	private void handleEvent(AbstractTaskEvent event, Consumer<Zone> handler) {
 		if (event.getDvrpMode().equals(mode) && event.getTaskType().equals(DrtStayTask.TYPE)) {
-			Zone zone = zonalSystem.getZoneForLinkId(event.getLinkId());
-			if (zone != null) {
-				handler.accept(zone);
-			}
+			zonalSystem.getZoneForLinkId(event.getLinkId()).ifPresent(handler);
 		}
 	}
 
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/RebalancingParams.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/RebalancingParams.java
index 07b422912d9..0cc73da2bb4 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/RebalancingParams.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/RebalancingParams.java
@@ -21,7 +21,7 @@
 import org.matsim.contrib.drt.optimizer.rebalancing.Feedforward.FeedforwardRebalancingStrategyParams;
 import org.matsim.contrib.drt.optimizer.rebalancing.mincostflow.MinCostFlowRebalancingStrategyParams;
 import org.matsim.contrib.drt.optimizer.rebalancing.plusOne.PlusOneRebalancingStrategyParams;
-import org.matsim.contrib.util.ReflectiveConfigGroupWithConfigurableParameterSets;
+import org.matsim.contrib.common.util.ReflectiveConfigGroupWithConfigurableParameterSets;
 import org.matsim.core.config.Config;
 import org.matsim.core.config.ConfigGroup;
 
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/RebalancingUtils.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/RebalancingUtils.java
index 8584026e706..bc712ab59f9 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/RebalancingUtils.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/RebalancingUtils.java
@@ -20,12 +20,6 @@
 
 package org.matsim.contrib.drt.optimizer.rebalancing;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Stream;
-
 import org.matsim.api.core.v01.Id;
 import org.matsim.api.core.v01.network.Link;
 import org.matsim.contrib.common.zones.Zone;
@@ -38,27 +32,31 @@
 import org.matsim.contrib.dvrp.schedule.StayTask;
 import org.matsim.contrib.dvrp.schedule.Task;
 
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
 /**
  * @author Michal Maciejewski (michalm)
  */
 public class RebalancingUtils {
-	public static Map<Zone, List<DvrpVehicle>> groupRebalancableVehicles(ZoneSystem zonalSystem,
+	public static Map<Zone, List<DvrpVehicle>> groupRebalancableVehicles(ZoneSystem zoneSystem,
 																		 RebalancingParams params, Stream<? extends DvrpVehicle> rebalancableVehicles, double time) {
 		Map<Zone, List<DvrpVehicle>> rebalancableVehiclesPerZone = new HashMap<>();
 		rebalancableVehicles.filter(v -> v.getServiceEndTime() > time + params.minServiceTime).forEach(v -> {
 			Link link = ((StayTask)v.getSchedule().getCurrentTask()).getLink();
-			Zone zone = zonalSystem.getZoneForLinkId(link.getId());
-			if (zone == null) {
-				zone = ZoneImpl.createDummyZone(Id.create("single-vehicle-zone-" + v.getId(), Zone.class), List.of(link),
-						link.getToNode().getCoord());
-			}
+			Zone zone = zoneSystem.getZoneForLinkId(link.getId())
+				.orElse(ZoneImpl.createDummyZone(Id.create("single-vehicle-zone-" + v.getId(), Zone.class),
+				link.getToNode().getCoord()));
 			rebalancableVehiclesPerZone.computeIfAbsent(zone, z -> new ArrayList<>()).add(v);
 		});
 		return rebalancableVehiclesPerZone;
 	}
 
 	// also include vehicles being right now relocated or recharged
-	public static Map<Zone, List<DvrpVehicle>> groupSoonIdleVehicles(ZoneSystem zonalSystem,
+	public static Map<Zone, List<DvrpVehicle>> groupSoonIdleVehicles(ZoneSystem zoneSystem,
 			RebalancingParams params, Fleet fleet, double time) {
 		Map<Zone, List<DvrpVehicle>> soonIdleVehiclesPerZone = new HashMap<>();
 		for (DvrpVehicle v : fleet.getVehicles().values()) {
@@ -67,10 +65,10 @@ public static Map<Zone, List<DvrpVehicle>> groupSoonIdleVehicles(ZoneSystem zona
 			if (stayTask.getStatus() == Task.TaskStatus.PLANNED
 					&& stayTask.getBeginTime() < time + params.maxTimeBeforeIdle
 					&& v.getServiceEndTime() > time + params.minServiceTime) {
-				Zone zone = zonalSystem.getZoneForLinkId(stayTask.getLink().getId());
-				if (zone != null) {
-					soonIdleVehiclesPerZone.computeIfAbsent(zone, z -> new ArrayList<>()).add(v);
-				}
+				zoneSystem.getZoneForLinkId(stayTask.getLink().getId())
+					.ifPresent(
+						zone -> soonIdleVehiclesPerZone.computeIfAbsent(zone, z -> new ArrayList<>()).add(v)
+					);
 			}
 		}
 		return soonIdleVehiclesPerZone;
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/demandestimator/NetDepartureReplenishDemandEstimator.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/demandestimator/NetDepartureReplenishDemandEstimator.java
index cc1c0b41dd5..0ebce86e3de 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/demandestimator/NetDepartureReplenishDemandEstimator.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/demandestimator/NetDepartureReplenishDemandEstimator.java
@@ -52,8 +52,8 @@ public void handleEvent(DrtRequestSubmittedEvent event) {
 		if (event.getMode().equals(mode)) {
 			// At the submission time, this is only a potential trip.
 			int timeBin = (int)Math.floor(event.getTime() / timeBinSize);
-			Zone departureZoneId = zonalSystem.getZoneForLinkId(event.getFromLinkId());
-			Zone arrivalZoneId = zonalSystem.getZoneForLinkId(event.getToLinkId());
+			Zone departureZoneId = zonalSystem.getZoneForLinkId(event.getFromLinkId()).orElseThrow();
+			Zone arrivalZoneId = zonalSystem.getZoneForLinkId(event.getToLinkId()).orElseThrow();
 			potentialDrtTripsMap.put(event.getRequestId(), new Trip(timeBin, departureZoneId, arrivalZoneId));
 		}
 	}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/demandestimator/PreviousIterationDrtDemandEstimator.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/demandestimator/PreviousIterationDrtDemandEstimator.java
index f6d2f17745d..5c7aa4ca6f4 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/demandestimator/PreviousIterationDrtDemandEstimator.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/demandestimator/PreviousIterationDrtDemandEstimator.java
@@ -69,17 +69,16 @@ public void reset(int iteration) {
 	@Override
 	public void handleEvent(PersonDepartureEvent event) {
 		if (event.getLegMode().equals(mode)) {
-			Zone zone = zonalSystem.getZoneForLinkId(event.getLinkId());
-			if (zone == null) {
+			zonalSystem.getZoneForLinkId(event.getLinkId()).ifPresentOrElse(
+                    zone -> {
+                        int timeBin = getBinForTime(event.getTime());
+                        currentIterationDepartures.computeIfAbsent(timeBin, v -> new HashMap<>())
+                            .computeIfAbsent(zone, z -> new MutableInt())
+                            .increment();
+                    },
 				//might be that somebody walks into the service area or that service area is larger/different than DrtZonalSystem...
-				logger.warn("No zone found for linkId " + event.getLinkId().toString());
-				return;
-			}
-
-			int timeBin = getBinForTime(event.getTime());
-			currentIterationDepartures.computeIfAbsent(timeBin, v -> new HashMap<>())
-					.computeIfAbsent(zone, z -> new MutableInt())
-					.increment();
+				() -> logger.warn("No zone found for linkId " + event.getLinkId().toString())
+			);
 		}
 	}
 
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/targetcalculator/EqualVehiclesToPopulationRatioTargetCalculator.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/targetcalculator/EqualVehiclesToPopulationRatioTargetCalculator.java
index 7968af6e705..a3e587983b9 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/targetcalculator/EqualVehiclesToPopulationRatioTargetCalculator.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/targetcalculator/EqualVehiclesToPopulationRatioTargetCalculator.java
@@ -35,7 +35,7 @@
 
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
+import java.util.Optional;
 import java.util.function.ToDoubleFunction;
 import java.util.stream.Collectors;
 
@@ -73,7 +73,8 @@ private Map<Zone, Integer> countFirstActsPerZone(ZoneSystem zonalSystem, Populat
 				.stream()
 				.map(person -> (Activity)person.getSelectedPlan().getPlanElements().get(0))
 				.map(activity -> zonalSystem.getZoneForLinkId(activity.getLinkId()))
-				.filter(Objects::nonNull)
+				.filter(Optional::isPresent)
+				.map(Optional::get)
 				.collect(Collectors.groupingBy(zone -> zone, collectingAndThen(counting(), Long::intValue)));
 	}
 
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtConfigGroup.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtConfigGroup.java
index 93597ab6271..7d6f0b9cef4 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtConfigGroup.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtConfigGroup.java
@@ -29,7 +29,7 @@
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.matsim.api.core.v01.TransportMode;
-import org.matsim.contrib.drt.analysis.zonal.DrtZonalSystemParams;
+import org.matsim.contrib.drt.analysis.zonal.DrtZoneSystemParams;
 import org.matsim.contrib.drt.fare.DrtFareParams;
 import org.matsim.contrib.drt.optimizer.DrtRequestInsertionRetryParams;
 import org.matsim.contrib.drt.optimizer.insertion.DrtInsertionSearchParams;
@@ -42,7 +42,7 @@
 import org.matsim.contrib.drt.speedup.DrtSpeedUpParams;
 import org.matsim.contrib.dvrp.router.DvrpModeRoutingNetworkModule;
 import org.matsim.contrib.dvrp.run.Modal;
-import org.matsim.contrib.util.ReflectiveConfigGroupWithConfigurableParameterSets;
+import org.matsim.contrib.common.util.ReflectiveConfigGroupWithConfigurableParameterSets;
 import org.matsim.core.config.Config;
 import org.matsim.core.config.groups.ScoringConfigGroup;
 import org.matsim.core.config.groups.RoutingConfigGroup;
@@ -220,7 +220,7 @@ public enum OperationalScheme {
 	private DrtInsertionSearchParams drtInsertionSearchParams;
 
 	@Nullable
-	private DrtZonalSystemParams zonalSystemParams;
+	private DrtZoneSystemParams zonalSystemParams;
 
 	@Nullable
 	private RebalancingParams rebalancingParams;
@@ -248,8 +248,8 @@ private void initSingletonParameterSets() {
 				params -> rebalancingParams = (RebalancingParams)params);
 
 		//zonal system (optional)
-		addDefinition(DrtZonalSystemParams.SET_NAME, DrtZonalSystemParams::new, () -> zonalSystemParams,
-				params -> zonalSystemParams = (DrtZonalSystemParams)params);
+		addDefinition(DrtZoneSystemParams.SET_NAME, DrtZoneSystemParams::new, () -> zonalSystemParams,
+				params -> zonalSystemParams = (DrtZoneSystemParams)params);
 
 		//insertion search params (one of: extensive, selective, repeated selective)
 		addDefinition(ExtensiveInsertionSearchParams.SET_NAME, ExtensiveInsertionSearchParams::new,
@@ -340,7 +340,7 @@ public DrtInsertionSearchParams getDrtInsertionSearchParams() {
 		return drtInsertionSearchParams;
 	}
 
-	public Optional<DrtZonalSystemParams> getZonalSystemParams() {
+	public Optional<DrtZoneSystemParams> getZonalSystemParams() {
 		return Optional.ofNullable(zonalSystemParams);
 	}
 
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/analysis/zonal/DrtGridUtilsTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/analysis/zonal/DrtGridUtilsTest.java
index 0d73adba5c6..0d5ca129c11 100644
--- a/contribs/drt/src/test/java/org/matsim/contrib/drt/analysis/zonal/DrtGridUtilsTest.java
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/analysis/zonal/DrtGridUtilsTest.java
@@ -1,44 +1,42 @@
 package org.matsim.contrib.drt.analysis.zonal;
 
-import static org.assertj.core.api.Assertions.assertThat;
-
-import java.util.Map;
-
 import org.junit.jupiter.api.Test;
 import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.prep.PreparedGeometry;
 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.api.core.v01.network.Network;
 import org.matsim.api.core.v01.network.Node;
+import org.matsim.contrib.common.zones.Zone;
+import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystem;
 import org.matsim.core.network.NetworkUtils;
 
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
 public class DrtGridUtilsTest {
 
 	@Test
 	void test() {
 		Network network = createNetwork();
-		Map<String, PreparedGeometry> grid = DrtGridUtils.createGridFromNetwork(network, 100);
+		SquareGridZoneSystem squareGridZoneSystem = new SquareGridZoneSystem(network, 100, false, z -> true);
 
-		assertThat(grid).hasSize(100);
+		assertThat(squareGridZoneSystem.getZones()).hasSize(100);
 
-		int cell = 1;
 		for (int col = 0; col < 10; col++) {
 			for (int row = 0; row < 10; row++) {
-				Geometry geometry = grid.get(cell + "").getGeometry();
+				Optional<Zone> zoneForCoord = squareGridZoneSystem.getZoneForCoord(new Coord(col * 100, row * 100));
 
-				assertThat(geometry.getCoordinates()).containsExactly(//
+				assertThat(zoneForCoord).isPresent();
+				assertThat(zoneForCoord.get().getPreparedGeometry().getGeometry().getCoordinates()).containsExactly(//
 						new Coordinate(col * 100, row * 100),//
 						new Coordinate(col * 100 + 100, row * 100),//
 						new Coordinate(col * 100 + 100, row * 100 + 100),//
 						new Coordinate(col * 100, row * 100 + 100),//
 						new Coordinate(col * 100, row * 100));
-				assertThat(geometry.getCentroid().getCoordinate()).isEqualTo(
+				assertThat(zoneForCoord.get().getPreparedGeometry().getGeometry().getCentroid().getCoordinate()).isEqualTo(
 						new Coordinate(col * 100 + 50, row * 100 + 50));
-
-				cell++;
 			}
 		}
 	}
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/analysis/zonal/DrtZonalSystemTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/analysis/zonal/DrtZonalSystemTest.java
index e17d38c7901..eb2ca828904 100644
--- a/contribs/drt/src/test/java/org/matsim/contrib/drt/analysis/zonal/DrtZonalSystemTest.java
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/analysis/zonal/DrtZonalSystemTest.java
@@ -29,14 +29,14 @@
 import org.locationtech.jts.geom.prep.PreparedGeometryFactory;
 import org.matsim.api.core.v01.Id;
 import org.matsim.api.core.v01.network.Link;
-import org.matsim.contrib.common.zones.ZoneSystem;
+import org.matsim.contrib.common.zones.Zone;
+import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystem;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
+import java.util.function.Predicate;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.matsim.contrib.common.zones.ZoneSystemUtils.createFromPreparedGeometries;
 import static org.matsim.contrib.drt.analysis.zonal.DrtGridUtilsTest.createNetwork;
 
 /**
@@ -46,16 +46,14 @@ public class DrtZonalSystemTest {
 
 	@Test
 	void test_cellSize100() {
-		ZoneSystem drtZonalSystem = createFromPreparedGeometries(createNetwork(),
-				DrtGridUtils.createGridFromNetwork(createNetwork(), 100));
-		Assertions.assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId("ab")).getId().toString()).isEqualTo("10");
+		SquareGridZoneSystem drtZonalSystem = new SquareGridZoneSystem(createNetwork(), 100);
+		Assertions.assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId("ab")).orElseThrow().getId().toString()).isEqualTo("90");
 	}
 
 	@Test
 	void test_cellSize700() {
-		ZoneSystem drtZonalSystem = createFromPreparedGeometries(createNetwork(),
-				DrtGridUtils.createGridFromNetwork(createNetwork(), 700));
-		Assertions.assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId("ab")).getId().toString()).isEqualTo("2");
+		SquareGridZoneSystem drtZonalSystem = new SquareGridZoneSystem(createNetwork(), 700);
+		Assertions.assertThat(drtZonalSystem.getZoneForLinkId(Id.createLinkId("ab")).orElseThrow().getId().toString()).isEqualTo("2");
 	}
 
 	@Test
@@ -63,15 +61,15 @@ void test_gridWithinServiceArea(){
 		Coordinate min = new Coordinate(-500, 500);
 		Coordinate max = new Coordinate(1500, 1500);
 		List<PreparedGeometry> serviceArea = createServiceArea(min,max);
-		Map<String, PreparedGeometry> grid = DrtGridUtils.filterGridWithinServiceArea(DrtGridUtils.createGridFromNetwork(createNetwork(), 100), serviceArea);
-		ZoneSystem zonalSystem = createFromPreparedGeometries(createNetwork(),
-				grid);
+
+		Predicate<Zone> zoneFilter = zone -> serviceArea.stream().anyMatch(area -> area.intersects(zone.getPreparedGeometry().getGeometry()));
+		SquareGridZoneSystem zonalSystem = new SquareGridZoneSystem(createNetwork(), 100, zoneFilter);
 
 		assertEquals(2, zonalSystem.getZones().size());
 
 		//link 'da' is outside of the service area
 		Id<Link> id = Id.createLinkId("da");
-		Assertions.assertThat(zonalSystem.getZoneForLinkId(id)).isNull();
+		Assertions.assertThat(zonalSystem.getZoneForLinkId(id)).isNotPresent();
 	}
 
 	@Test
@@ -79,9 +77,9 @@ void test_noZonesWithoutLinks(){
 		Coordinate min = new Coordinate(1500, 1500);
 		Coordinate max = new Coordinate(2500, 2500);
 		List<PreparedGeometry> serviceArea = createServiceArea(min,max);
-		Map<String, PreparedGeometry> grid = DrtGridUtils.filterGridWithinServiceArea(DrtGridUtils.createGridFromNetwork(createNetwork(), 100), serviceArea);
-		ZoneSystem zonalSystem = createFromPreparedGeometries(createNetwork(),
-				grid);
+
+		Predicate<Zone> zoneFilter = zone -> serviceArea.stream().anyMatch(area -> area.intersects(zone.getPreparedGeometry().getGeometry()));
+		SquareGridZoneSystem zonalSystem = new SquareGridZoneSystem(createNetwork(), 100, zoneFilter);
 
 		//service area is off the network - so we should have 0 zones..
 		assertEquals(0, zonalSystem.getZones().size());
@@ -100,7 +98,4 @@ public List<PreparedGeometry> createServiceArea(Coordinate min, Coordinate max){
 		ServiceArea.add(preparedGeometryFactory.create(polygon));
 		return ServiceArea;
 	}
-
-
-
 }
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/analysis/zonal/RandomDrtZoneTargetLinkSelectorTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/analysis/zonal/RandomDrtZoneTargetLinkSelectorTest.java
index f77e5fa57a3..8a31e2470db 100644
--- a/contribs/drt/src/test/java/org/matsim/contrib/drt/analysis/zonal/RandomDrtZoneTargetLinkSelectorTest.java
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/analysis/zonal/RandomDrtZoneTargetLinkSelectorTest.java
@@ -20,34 +20,41 @@
 
 package org.matsim.contrib.drt.analysis.zonal;
 
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.util.List;
-import java.util.function.IntUnaryOperator;
-
 import org.junit.jupiter.api.Test;
+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.api.core.v01.network.Network;
+import org.matsim.api.core.v01.network.Node;
 import org.matsim.contrib.common.zones.Zone;
 import org.matsim.contrib.common.zones.ZoneImpl;
-import org.matsim.testcases.fakes.FakeLink;
+import org.matsim.contrib.common.zones.ZoneSystemImpl;
+import org.matsim.core.network.NetworkUtils;
 import org.mockito.ArgumentCaptor;
 
+import java.util.List;
+import java.util.Optional;
+import java.util.function.IntUnaryOperator;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 /**
  * @author Michal Maciejewski (michalm)
  */
 public class RandomDrtZoneTargetLinkSelectorTest {
 
-	private final Link link0 = new FakeLink(Id.createLinkId("0"));
-	private final Link link1 = new FakeLink(Id.createLinkId("1"));
-	private final Link link2 = new FakeLink(Id.createLinkId("2"));
-	private final Link link3 = new FakeLink(Id.createLinkId("3"));
+	private static final Id<Link> LINK_ID_0 = Id.createLinkId("0");
+	private static final Id<Link> LINK_ID_1 = Id.createLinkId("1");
+	private static final Id<Link> LINK_ID_2 = Id.createLinkId("2");
+	private static final Id<Link> LINK_ID_3 = Id.createLinkId("3");
 
 	@Test
 	void testSelectTargetLink_fourLinks() {
-		Zone zone = ZoneImpl.createDummyZone(Id.create("zone", Zone.class), List.of(link0, link1, link2, link3), null);
+		Zone zone = ZoneImpl.createDummyZone(Id.create("zone", Zone.class), null);
+
+		Network network = createNetwork();
 
 		//fake random sequence
 		IntUnaryOperator random = mock(IntUnaryOperator.class);
@@ -55,13 +62,35 @@ void testSelectTargetLink_fourLinks() {
 		when(random.applyAsInt(boundCaptor.capture())).thenReturn(0, 3, 1, 2);
 
 		//test selected target links
-		RandomDrtZoneTargetLinkSelector selector = new RandomDrtZoneTargetLinkSelector(random);
-		assertThat(selector.selectTargetLink(zone)).isEqualTo(link0);
-		assertThat(selector.selectTargetLink(zone)).isEqualTo(link3);
-		assertThat(selector.selectTargetLink(zone)).isEqualTo(link1);
-		assertThat(selector.selectTargetLink(zone)).isEqualTo(link2);
+		RandomDrtZoneTargetLinkSelector selector = new RandomDrtZoneTargetLinkSelector(new ZoneSystemImpl(List.of(zone), coord -> Optional.of(zone), network), random);
+		assertThat(selector.selectTargetLink(zone)).isEqualTo(network.getLinks().get(LINK_ID_0));
+		assertThat(selector.selectTargetLink(zone)).isEqualTo(network.getLinks().get(LINK_ID_3));
+		assertThat(selector.selectTargetLink(zone)).isEqualTo(network.getLinks().get(LINK_ID_1));
+		assertThat(selector.selectTargetLink(zone)).isEqualTo(network.getLinks().get(LINK_ID_2));
 
 		//check if correct values were passed to Random as the nextInt() bounds (== link count)
 		assertThat(boundCaptor.getAllValues()).containsExactly(4, 4, 4, 4);
 	}
+
+	static Network createNetwork() {
+		Network network = NetworkUtils.createNetwork();
+		Node a = network.getFactory().createNode(Id.createNodeId("a"), new Coord(0, 0));
+		Node b = network.getFactory().createNode(Id.createNodeId("b"), new Coord(0, 1000));
+		Node c = network.getFactory().createNode(Id.createNodeId("c"), new Coord(1000, 1000));
+		Node d = network.getFactory().createNode(Id.createNodeId("d"), new Coord(1000, 0));
+		network.addNode(a);
+		network.addNode(b);
+		network.addNode(c);
+		network.addNode(d);
+
+		Link ab = network.getFactory().createLink(LINK_ID_0, a, b);
+		Link bc = network.getFactory().createLink(LINK_ID_1, b, c);
+		Link cd = network.getFactory().createLink(LINK_ID_2, c, d);
+		Link da = network.getFactory().createLink(LINK_ID_3, d, a);
+		network.addLink(ab);
+		network.addLink(bc);
+		network.addLink(cd);
+		network.addLink(da);
+		return network;
+	}
 }
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/MaxDetourConstraintTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/MaxDetourConstraintTest.java
index 83ffce4ddb5..ec0e2a1f030 100644
--- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/MaxDetourConstraintTest.java
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/MaxDetourConstraintTest.java
@@ -2,10 +2,12 @@
 
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
+import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystemParams;
 import org.matsim.contrib.drt.run.DrtConfigGroup;
 import org.matsim.contrib.drt.run.DrtControlerCreator;
 import org.matsim.contrib.drt.run.MultiModeDrtConfigGroup;
 import org.matsim.contrib.dvrp.run.DvrpConfigGroup;
+import org.matsim.contrib.zone.skims.DvrpTravelTimeMatrixParams;
 import org.matsim.core.config.Config;
 import org.matsim.core.config.ConfigUtils;
 import org.matsim.core.controler.Controler;
@@ -24,7 +26,12 @@ public class MaxDetourConstraintTest {
 	@Test
 	public void testMaxDetourConstraint() {
 		URL configUrl = IOUtils.extendUrl(ExamplesUtils.getTestScenarioURL("mielec"), "mielec_drt_config.xml");
-		Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), new DvrpConfigGroup(),
+
+		DvrpConfigGroup dvrpConfig = new DvrpConfigGroup();
+		DvrpTravelTimeMatrixParams matrixParams = dvrpConfig.getTravelTimeMatrixParams();
+		matrixParams.addParameterSet(matrixParams.createParameterSet(SquareGridZoneSystemParams.SET_NAME));
+
+		Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), dvrpConfig,
 			new OTFVisConfigGroup());
 		DrtConfigGroup drtConfigGroup = DrtConfigGroup.getSingleModeDrtConfig(config);
 
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/DrtPoolingParameterTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/DrtPoolingParameterTest.java
index 76b6f176cde..8451f93b1e2 100644
--- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/DrtPoolingParameterTest.java
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/DrtPoolingParameterTest.java
@@ -9,12 +9,14 @@
 import org.matsim.api.core.v01.Id;
 import org.matsim.api.core.v01.Scenario;
 import org.matsim.api.core.v01.population.*;
+import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystemParams;
 import org.matsim.contrib.drt.run.DrtControlerCreator;
 import org.matsim.contrib.drt.run.MultiModeDrtConfigGroup;
 import org.matsim.contrib.dvrp.fleet.DvrpVehicle;
 import org.matsim.contrib.dvrp.passenger.PassengerRequestScheduledEvent;
 import org.matsim.contrib.dvrp.passenger.PassengerRequestScheduledEventHandler;
 import org.matsim.contrib.dvrp.run.DvrpConfigGroup;
+import org.matsim.contrib.zone.skims.DvrpTravelTimeMatrixParams;
 import org.matsim.core.api.experimental.events.EventsManager;
 import org.matsim.core.config.Config;
 import org.matsim.core.config.ConfigUtils;
@@ -208,7 +210,12 @@ void testBetaOneVehicleForFourAgents() {
 	private PersonEnterDrtVehicleEventHandler setupAndRunScenario(double maxWaitTime, double maxTravelTimeAlpha,
 			double maxTravelTimeBeta) {
 		URL configUrl = IOUtils.extendUrl(ExamplesUtils.getTestScenarioURL("mielec"), "mielec_drt_config.xml");
-		Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), new DvrpConfigGroup(),
+
+		DvrpConfigGroup dvrpConfig = new DvrpConfigGroup();
+		DvrpTravelTimeMatrixParams matrixParams = dvrpConfig.getTravelTimeMatrixParams();
+		matrixParams.addParameterSet(matrixParams.createParameterSet(SquareGridZoneSystemParams.SET_NAME));
+
+		Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), dvrpConfig,
 				new OTFVisConfigGroup());
 
 		config.plans().setInputFile(null);
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/rebalancing/demandestimator/PreviousIterationDrtDemandEstimatorTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/rebalancing/demandestimator/PreviousIterationDrtDemandEstimatorTest.java
index 0b93d725f48..7f43fd42d8b 100644
--- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/rebalancing/demandestimator/PreviousIterationDrtDemandEstimatorTest.java
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/rebalancing/demandestimator/PreviousIterationDrtDemandEstimatorTest.java
@@ -20,23 +20,26 @@
 
 package org.matsim.contrib.drt.optimizer.rebalancing.demandestimator;
 
-import static org.assertj.core.api.Assertions.assertThat;
-
-import java.util.List;
-
 import org.junit.jupiter.api.Test;
 import org.matsim.api.core.v01.Coord;
 import org.matsim.api.core.v01.Id;
 import org.matsim.api.core.v01.TransportMode;
 import org.matsim.api.core.v01.events.PersonDepartureEvent;
 import org.matsim.api.core.v01.network.Link;
+import org.matsim.api.core.v01.network.Network;
+import org.matsim.api.core.v01.network.Node;
 import org.matsim.contrib.common.zones.Zone;
 import org.matsim.contrib.common.zones.ZoneImpl;
 import org.matsim.contrib.common.zones.ZoneSystem;
 import org.matsim.contrib.common.zones.ZoneSystemImpl;
 import org.matsim.contrib.drt.optimizer.rebalancing.RebalancingParams;
 import org.matsim.contrib.drt.run.DrtConfigGroup;
-import org.matsim.testcases.fakes.FakeLink;
+import org.matsim.core.network.NetworkUtils;
+
+import java.util.List;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
 
 /**
  * @author michalm (Michal Maciejewski)
@@ -45,12 +48,22 @@ public class PreviousIterationDrtDemandEstimatorTest {
 
 	private static final int ESTIMATION_PERIOD = 1800;
 
-	private final Link link1 = new FakeLink(Id.createLinkId("link_1"));
-	private final Link link2 = new FakeLink(Id.createLinkId("link_2"));
+	private final Network network = createNetwork();
+
+	private final Link link1 = network.getLinks().get(Id.createLinkId("link_1"));
+	private final Link link2 = network.getLinks().get(Id.createLinkId("link_2"));
 
-	private final Zone zone1 = ZoneImpl.createDummyZone(Id.create("zone_1", Zone.class), List.of(link1), new Coord());
-	private final Zone zone2 = ZoneImpl.createDummyZone(Id.create("zone_2", Zone.class), List.of(link2), new Coord());
-	private final ZoneSystem zonalSystem = new ZoneSystemImpl(List.of(zone1, zone2));
+	private final Zone zone1 = ZoneImpl.createDummyZone(Id.create("zone_1", Zone.class), new Coord());
+	private final Zone zone2 = ZoneImpl.createDummyZone(Id.create("zone_2", Zone.class), new Coord());
+	private final ZoneSystem zonalSystem = new ZoneSystemImpl(List.of(zone1, zone2), coord -> {
+        if(coord == link1.getToNode().getCoord()) {
+            return Optional.of(zone1);
+        } else if(coord == link2.getToNode().getCoord()) {
+            return Optional.of(zone2);
+        } else {
+            throw new RuntimeException();
+        }
+    }, network);
 
 	@Test
 	void noDepartures() {
@@ -177,4 +190,18 @@ private void assertDemand(PreviousIterationDrtDemandEstimator estimator, double
 		assertThat(estimator.getExpectedDemand(fromTime, ESTIMATION_PERIOD).applyAsDouble(zone)).isEqualTo(
 				expectedDemand);
 	}
+
+	static Network createNetwork() {
+		Network network = NetworkUtils.createNetwork();
+		Node a = network.getFactory().createNode(Id.createNodeId("a"), new Coord());
+		Node b = network.getFactory().createNode(Id.createNodeId("b"), new Coord());
+		network.addNode(a);
+		network.addNode(b);
+
+		Link ab = network.getFactory().createLink(Id.createLinkId("link_1"), a, b);
+		Link bc = network.getFactory().createLink(Id.createLinkId("link_2"), b, a);
+		network.addLink(ab);
+		network.addLink(bc);
+		return network;
+	}
 }
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/rebalancing/targetcalculator/EqualVehicleDensityTargetCalculatorTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/rebalancing/targetcalculator/EqualVehicleDensityTargetCalculatorTest.java
index e1ef42a98d1..7e462166a98 100644
--- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/rebalancing/targetcalculator/EqualVehicleDensityTargetCalculatorTest.java
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/rebalancing/targetcalculator/EqualVehicleDensityTargetCalculatorTest.java
@@ -26,8 +26,7 @@
 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.ZoneSystemUtils;
-import org.matsim.contrib.drt.analysis.zonal.DrtGridUtils;
+import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystem;
 import org.matsim.contrib.drt.run.MultiModeDrtConfigGroup;
 import org.matsim.contrib.dvrp.fleet.DvrpVehicle;
 import org.matsim.contrib.dvrp.fleet.FleetSpecification;
@@ -42,8 +41,6 @@
 import java.util.Map;
 import java.util.function.ToDoubleFunction;
 
-import static org.assertj.core.api.Assertions.assertThat;
-
 /**
  * @author Michal Maciejewski (michalm)
  */
@@ -55,8 +52,8 @@ public class EqualVehicleDensityTargetCalculatorTest {
 	private final Network network = NetworkUtils.readNetwork(
 			config.network().getInputFileURL(config.getContext()).toString());
 
-	private final ZoneSystem zonalSystem = ZoneSystemUtils.createFromPreparedGeometries(network,
-			DrtGridUtils.createGridFromNetwork(network, 500.));
+	private final ZoneSystem zonalSystem = new SquareGridZoneSystem(network, 500.);
+
 
 	@Test
 	void calculate_oneVehiclePerZone() {
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/rebalancing/targetcalculator/EqualVehiclesToPopulationRatioTargetCalculatorTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/rebalancing/targetcalculator/EqualVehiclesToPopulationRatioTargetCalculatorTest.java
index 7c24bd07c26..fff34e1b7d0 100644
--- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/rebalancing/targetcalculator/EqualVehiclesToPopulationRatioTargetCalculatorTest.java
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/rebalancing/targetcalculator/EqualVehiclesToPopulationRatioTargetCalculatorTest.java
@@ -20,11 +20,6 @@
 
 package org.matsim.contrib.drt.optimizer.rebalancing.targetcalculator;
 
-import static org.assertj.core.api.Assertions.assertThat;
-
-import java.util.Map;
-import java.util.function.ToDoubleFunction;
-
 import org.junit.jupiter.api.Test;
 import org.matsim.api.core.v01.Id;
 import org.matsim.api.core.v01.network.Link;
@@ -36,7 +31,7 @@
 import org.matsim.contrib.common.zones.Zone;
 import org.matsim.contrib.common.zones.ZoneSystem;
 import org.matsim.contrib.common.zones.ZoneSystemUtils;
-import org.matsim.contrib.drt.analysis.zonal.DrtGridUtils;
+import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystem;
 import org.matsim.contrib.drt.run.MultiModeDrtConfigGroup;
 import org.matsim.contrib.dvrp.fleet.DvrpVehicle;
 import org.matsim.contrib.dvrp.fleet.FleetSpecification;
@@ -49,6 +44,11 @@
 import org.matsim.core.utils.io.IOUtils;
 import org.matsim.examples.ExamplesUtils;
 
+import java.util.Map;
+import java.util.function.ToDoubleFunction;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
 /**
  * @author Michal Maciejewski (michalm)
  */
@@ -60,44 +60,42 @@ public class EqualVehiclesToPopulationRatioTargetCalculatorTest {
 	private final Network network = NetworkUtils.readNetwork(
 			config.network().getInputFileURL(config.getContext()).toString());
 
-	private final ZoneSystem zonalSystem = ZoneSystemUtils.createFromPreparedGeometries(network,
-			DrtGridUtils.createGridFromNetwork(network, 500.));
-
+	private final ZoneSystem zonalSystem = new SquareGridZoneSystem(network, 500.);
 	private final Population population = PopulationUtils.createPopulation(config);
 	private final PopulationFactory factory = population.getFactory();
 
 	@Test
 	void testCalculate_oneVehiclePerZone() {
-		initPopulation(Map.of(ZoneSystemUtils.createZoneId("2"), 1, ZoneSystemUtils.createZoneId("4"), 1, ZoneSystemUtils.createZoneId("8"), 1));
+		initPopulation(Map.of(ZoneSystemUtils.createZoneId("1"), 1, ZoneSystemUtils.createZoneId("3"), 1, ZoneSystemUtils.createZoneId("7"), 1));
 
 		var targetFunction = new EqualVehiclesToPopulationRatioTargetCalculator(zonalSystem, population,
 				createFleetSpecification(8)).calculate(0, Map.of());
 
-		assertTarget(targetFunction, zonalSystem, Id.create("1", Zone.class), 0);
-		assertTarget(targetFunction, zonalSystem, Id.create("2", Zone.class), 8. * (1. / 3));
-		assertTarget(targetFunction, zonalSystem, Id.create("3", Zone.class), 0);
-		assertTarget(targetFunction, zonalSystem, Id.create("4", Zone.class), 8. * (1. / 3));
+		assertTarget(targetFunction, zonalSystem, Id.create("0", Zone.class), 0);
+		assertTarget(targetFunction, zonalSystem, Id.create("1", Zone.class), 8. * (1. / 3));
+		assertTarget(targetFunction, zonalSystem, Id.create("2", Zone.class), 0);
+		assertTarget(targetFunction, zonalSystem, Id.create("3", Zone.class), 8. * (1. / 3));
+		assertTarget(targetFunction, zonalSystem, Id.create("4", Zone.class), 0);
 		assertTarget(targetFunction, zonalSystem, Id.create("5", Zone.class), 0);
 		assertTarget(targetFunction, zonalSystem, Id.create("6", Zone.class), 0);
-		assertTarget(targetFunction, zonalSystem, Id.create("7", Zone.class), 0);
-		assertTarget(targetFunction, zonalSystem, Id.create("8", Zone.class), 8. * (1. / 3));
+		assertTarget(targetFunction, zonalSystem, Id.create("7", Zone.class), 8. * (1. / 3));
 	}
 
 	@Test
 	void testCalculate_twoVehiclesPerZone() {
-		initPopulation(Map.of(ZoneSystemUtils.createZoneId("2"), 1, ZoneSystemUtils.createZoneId("4"), 1, ZoneSystemUtils.createZoneId("8"), 1));
+		initPopulation(Map.of(ZoneSystemUtils.createZoneId("1"), 1, ZoneSystemUtils.createZoneId("3"), 1, ZoneSystemUtils.createZoneId("7"), 1));
 
 		var targetFunction = new EqualVehiclesToPopulationRatioTargetCalculator(zonalSystem, population,
 				createFleetSpecification(16)).calculate(0, Map.of());
 
-		assertTarget(targetFunction, zonalSystem, Id.create("1", Zone.class), 0);
-		assertTarget(targetFunction, zonalSystem, Id.create("2", Zone.class), 16 * (1. / 3));
-		assertTarget(targetFunction, zonalSystem, Id.create("3", Zone.class), 0);
-		assertTarget(targetFunction, zonalSystem, Id.create("4", Zone.class), 16 * (1. / 3));
+		assertTarget(targetFunction, zonalSystem, Id.create("0", Zone.class), 0);
+		assertTarget(targetFunction, zonalSystem, Id.create("1", Zone.class), 16 * (1. / 3));
+		assertTarget(targetFunction, zonalSystem, Id.create("2", Zone.class), 0);
+		assertTarget(targetFunction, zonalSystem, Id.create("3", Zone.class), 16 * (1. / 3));
+		assertTarget(targetFunction, zonalSystem, Id.create("4", Zone.class), 0);
 		assertTarget(targetFunction, zonalSystem, Id.create("5", Zone.class), 0);
 		assertTarget(targetFunction, zonalSystem, Id.create("6", Zone.class), 0);
-		assertTarget(targetFunction, zonalSystem, Id.create("7", Zone.class), 0);
-		assertTarget(targetFunction, zonalSystem, Id.create("8", Zone.class), 16. * (1. / 3));
+		assertTarget(targetFunction, zonalSystem, Id.create("7", Zone.class), 16. * (1. / 3));
 	}
 
 	@Test
@@ -106,6 +104,7 @@ void testCalculate_noPopulation() {
 		var targetFunction = new EqualVehiclesToPopulationRatioTargetCalculator(zonalSystem, population,
 				createFleetSpecification(16)).calculate(0, Map.of());
 
+		assertTarget(targetFunction, zonalSystem, Id.create("0", Zone.class), 0);
 		assertTarget(targetFunction, zonalSystem, Id.create("1", Zone.class), 0);
 		assertTarget(targetFunction, zonalSystem, Id.create("2", Zone.class), 0);
 		assertTarget(targetFunction, zonalSystem, Id.create("3", Zone.class), 0);
@@ -113,23 +112,22 @@ void testCalculate_noPopulation() {
 		assertTarget(targetFunction, zonalSystem, Id.create("5", Zone.class), 0);
 		assertTarget(targetFunction, zonalSystem, Id.create("6", Zone.class), 0);
 		assertTarget(targetFunction, zonalSystem, Id.create("7", Zone.class), 0);
-		assertTarget(targetFunction, zonalSystem, Id.create("8", Zone.class), 0);
 	}
 
 	@Test
 	void testCalculate_unevenDistributionOfActivitiesInPopulatedZones() {
-		initPopulation(Map.of(ZoneSystemUtils.createZoneId("2"), 2, ZoneSystemUtils.createZoneId("4"), 4, ZoneSystemUtils.createZoneId("8"), 8));
+		initPopulation(Map.of(ZoneSystemUtils.createZoneId("1"), 2, ZoneSystemUtils.createZoneId("3"), 4, ZoneSystemUtils.createZoneId("7"), 8));
 		var targetFunction = new EqualVehiclesToPopulationRatioTargetCalculator(zonalSystem, population,
 				createFleetSpecification(16)).calculate(0, Map.of());
 
-		assertTarget(targetFunction, zonalSystem, Id.create("1", Zone.class), 0);
-		assertTarget(targetFunction, zonalSystem, Id.create("2", Zone.class), 16 * (2. / 14));
-		assertTarget(targetFunction, zonalSystem, Id.create("3", Zone.class), 0);
-		assertTarget(targetFunction, zonalSystem, Id.create("4", Zone.class), 16 * (4. / 14));
+		assertTarget(targetFunction, zonalSystem, Id.create("0", Zone.class), 0);
+		assertTarget(targetFunction, zonalSystem, Id.create("1", Zone.class), 16 * (2. / 14));
+		assertTarget(targetFunction, zonalSystem, Id.create("2", Zone.class), 0);
+		assertTarget(targetFunction, zonalSystem, Id.create("3", Zone.class), 16 * (4. / 14));
+		assertTarget(targetFunction, zonalSystem, Id.create("4", Zone.class), 0);
 		assertTarget(targetFunction, zonalSystem, Id.create("5", Zone.class), 0);
 		assertTarget(targetFunction, zonalSystem, Id.create("6", Zone.class), 0);
-		assertTarget(targetFunction, zonalSystem, Id.create("7", Zone.class), 0);
-		assertTarget(targetFunction, zonalSystem, Id.create("8", Zone.class), 16 * (8. / 14));
+		assertTarget(targetFunction, zonalSystem, Id.create("7", Zone.class), 16 * (8. / 14));
 	}
 
 	private FleetSpecification createFleetSpecification(int count) {
@@ -161,7 +159,7 @@ private void initPopulation(Map<Id<Zone>, Integer> populationPerZone) {
 	}
 
 	private void createAndAddPerson(String id, Id<Zone> zoneId) {
-		Id<Link> linkId = zonalSystem.getZones().get(zoneId).getLinks().get(0).getId();
+		Id<Link> linkId = zonalSystem.getLinksForZoneId(zoneId).get(0).getId();
 		Person person = factory.createPerson(Id.createPersonId(id));
 		Plan plan = factory.createPlan();
 		plan.addActivity(factory.createActivityFromLinkId("dummy", linkId));
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTestEnvironment.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTestEnvironment.java
index a80d8a42386..c97f2c34a81 100644
--- a/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTestEnvironment.java
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTestEnvironment.java
@@ -9,6 +9,7 @@
 import org.matsim.api.core.v01.network.NetworkFactory;
 import org.matsim.api.core.v01.network.Node;
 import org.matsim.api.core.v01.population.*;
+import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystemParams;
 import org.matsim.contrib.drt.optimizer.insertion.DrtInsertionSearchParams;
 import org.matsim.contrib.drt.optimizer.insertion.selective.SelectiveInsertionSearchParams;
 import org.matsim.contrib.drt.passenger.events.DrtRequestSubmittedEvent;
@@ -33,6 +34,7 @@
 import org.matsim.contrib.dvrp.vrpagent.TaskEndedEventHandler;
 import org.matsim.contrib.dvrp.vrpagent.TaskStartedEvent;
 import org.matsim.contrib.dvrp.vrpagent.TaskStartedEventHandler;
+import org.matsim.contrib.zone.skims.DvrpTravelTimeMatrixParams;
 import org.matsim.core.config.Config;
 import org.matsim.core.config.ConfigUtils;
 import org.matsim.core.config.groups.QSimConfigGroup.EndtimeInterpretation;
@@ -214,6 +216,8 @@ private void buildConfig(Config config) {
 		config.scoring().addActivityParams(genericParams);
 
 		DvrpConfigGroup dvrpConfig = new DvrpConfigGroup();
+		DvrpTravelTimeMatrixParams matrixParams = dvrpConfig.getTravelTimeMatrixParams();
+		matrixParams.addParameterSet(matrixParams.createParameterSet(SquareGridZoneSystemParams.SET_NAME));
 		config.addModule(dvrpConfig);
 
 		MultiModeDrtConfigGroup drtConfig = new MultiModeDrtConfigGroup();
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/run/examples/RunDrtExampleIT.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/run/examples/RunDrtExampleIT.java
index 652d66ecf19..9a75670e909 100644
--- a/contribs/drt/src/test/java/org/matsim/contrib/drt/run/examples/RunDrtExampleIT.java
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/run/examples/RunDrtExampleIT.java
@@ -33,6 +33,7 @@
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 import org.matsim.api.core.v01.Id;
+import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystemParams;
 import org.matsim.contrib.drt.optimizer.DrtRequestInsertionRetryParams;
 import org.matsim.contrib.drt.optimizer.insertion.repeatedselective.RepeatedSelectiveInsertionSearchParams;
 import org.matsim.contrib.drt.optimizer.insertion.selective.SelectiveInsertionSearchParams;
@@ -53,6 +54,7 @@
 import org.matsim.contrib.dvrp.passenger.PassengerRequestScheduledEventHandler;
 import org.matsim.contrib.dvrp.run.AbstractDvrpModeModule;
 import org.matsim.contrib.dvrp.run.DvrpConfigGroup;
+import org.matsim.contrib.zone.skims.DvrpTravelTimeMatrixParams;
 import org.matsim.core.config.Config;
 import org.matsim.core.config.ConfigUtils;
 import org.matsim.core.controler.AbstractModule;
@@ -77,8 +79,13 @@ public class RunDrtExampleIT {
 	@Test
 	void testRunDrtExampleWithNoRejections_ExtensiveSearch() {
 		Id.resetCaches();
+
+		DvrpConfigGroup dvrpConfigGroup = new DvrpConfigGroup();
+		DvrpTravelTimeMatrixParams matrixParams = dvrpConfigGroup.getTravelTimeMatrixParams();
+		matrixParams.addParameterSet(matrixParams.createParameterSet(SquareGridZoneSystemParams.SET_NAME));
+
 		URL configUrl = IOUtils.extendUrl(ExamplesUtils.getTestScenarioURL("mielec"), "mielec_drt_config.xml");
-		Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), new DvrpConfigGroup(),
+		Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), dvrpConfigGroup,
 				new OTFVisConfigGroup());
 
 		for (var drtCfg : MultiModeDrtConfigGroup.get(config).getModalElements()) {
@@ -93,8 +100,8 @@ void testRunDrtExampleWithNoRejections_ExtensiveSearch() {
 		var expectedStats = Stats.newBuilder()
 				.rejectionRate(0.0)
 				.rejections(0)
-				.waitAverage(296.95)
-				.inVehicleTravelTimeMean(387.02)
+				.waitAverage(297.19)
+				.inVehicleTravelTimeMean(386.78)
 				.totalTravelTimeMean(683.97)
 				.build();
 
@@ -105,7 +112,12 @@ void testRunDrtExampleWithNoRejections_ExtensiveSearch() {
 	void testRunDrtExampleWithNoRejections_SelectiveSearch() {
 		Id.resetCaches();
 		URL configUrl = IOUtils.extendUrl(ExamplesUtils.getTestScenarioURL("mielec"), "mielec_drt_config.xml");
-		Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), new DvrpConfigGroup(),
+
+		DvrpConfigGroup dvrpConfigGroup = new DvrpConfigGroup();
+		DvrpTravelTimeMatrixParams matrixParams = dvrpConfigGroup.getTravelTimeMatrixParams();
+		matrixParams.addParameterSet(matrixParams.createParameterSet(SquareGridZoneSystemParams.SET_NAME));
+
+		Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), dvrpConfigGroup,
 				new OTFVisConfigGroup());
 
 		for (var drtCfg : MultiModeDrtConfigGroup.get(config).getModalElements()) {
@@ -139,7 +151,12 @@ void testRunDrtExampleWithNoRejections_SelectiveSearch() {
 	void testRunDrtExampleWithNoRejections_RepeatedSelectiveSearch() {
 		Id.resetCaches();
 		URL configUrl = IOUtils.extendUrl(ExamplesUtils.getTestScenarioURL("mielec"), "mielec_drt_config.xml");
-		Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), new DvrpConfigGroup(),
+
+		DvrpConfigGroup dvrpConfigGroup = new DvrpConfigGroup();
+		DvrpTravelTimeMatrixParams matrixParams = dvrpConfigGroup.getTravelTimeMatrixParams();
+		matrixParams.addParameterSet(matrixParams.createParameterSet(SquareGridZoneSystemParams.SET_NAME));
+
+		Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), dvrpConfigGroup,
 			new OTFVisConfigGroup());
 
 		for (var drtCfg : MultiModeDrtConfigGroup.get(config).getModalElements()) {
@@ -162,9 +179,9 @@ void testRunDrtExampleWithNoRejections_RepeatedSelectiveSearch() {
 		var expectedStats = Stats.newBuilder()
 			.rejectionRate(0.0)
 			.rejections(0)
-			.waitAverage(261.57)
-			.inVehicleTravelTimeMean(382.74)
-			.totalTravelTimeMean(644.32)
+			.waitAverage(269.8)
+			.inVehicleTravelTimeMean(379.69)
+			.totalTravelTimeMean(649.49)
 			.build();
 
 		verifyDrtCustomerStatsCloseToExpectedStats(utils.getOutputDirectory(), expectedStats);
@@ -173,8 +190,13 @@ void testRunDrtExampleWithNoRejections_RepeatedSelectiveSearch() {
 	@Test
 	void testRunDrtExampleWithRequestRetry() {
 		Id.resetCaches();
+
+		DvrpConfigGroup dvrpConfig = new DvrpConfigGroup();
+		DvrpTravelTimeMatrixParams matrixParams = dvrpConfig.getTravelTimeMatrixParams();
+		matrixParams.addParameterSet(matrixParams.createParameterSet(SquareGridZoneSystemParams.SET_NAME));
+
 		URL configUrl = IOUtils.extendUrl(ExamplesUtils.getTestScenarioURL("mielec"), "mielec_drt_config.xml");
-		Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), new DvrpConfigGroup(),
+		Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), dvrpConfig,
 				new OTFVisConfigGroup());
 
 		for (var drtCfg : MultiModeDrtConfigGroup.get(config).getModalElements()) {
@@ -191,8 +213,8 @@ void testRunDrtExampleWithRequestRetry() {
 		var expectedStats = Stats.newBuilder()
 				.rejectionRate(0.0)
 				.rejections(1)
-				.waitAverage(305.97)
-				.inVehicleTravelTimeMean(378.18)
+				.waitAverage(306.21)
+				.inVehicleTravelTimeMean(377.94)
 				.totalTravelTimeMean(684.16)
 				.build();
 
@@ -270,11 +292,11 @@ void testRunServiceAreabasedExampleWithSpeedUp() {
 		RunDrtExample.run(config, false);
 
 		var expectedStats = Stats.newBuilder()
-				.rejectionRate(0.03)
-				.rejections(11)
-				.waitAverage(223.86)
-				.inVehicleTravelTimeMean(389.57)
-				.totalTravelTimeMean(613.44)
+				.rejectionRate(0.02)
+				.rejections(9)
+				.waitAverage(224.56)
+				.inVehicleTravelTimeMean(392.65)
+				.totalTravelTimeMean(617.21)
 				.build();
 
 		verifyDrtCustomerStatsCloseToExpectedStats(utils.getOutputDirectory(), expectedStats);
@@ -283,8 +305,13 @@ void testRunServiceAreabasedExampleWithSpeedUp() {
 	@Test
 	void testRunDrtExampleWithIncrementalStopDuration() {
 		Id.resetCaches();
+
+		DvrpConfigGroup dvrpConfig = new DvrpConfigGroup();
+		DvrpTravelTimeMatrixParams matrixParams = dvrpConfig.getTravelTimeMatrixParams();
+		matrixParams.addParameterSet(matrixParams.createParameterSet(SquareGridZoneSystemParams.SET_NAME));
+
 		URL configUrl = IOUtils.extendUrl(ExamplesUtils.getTestScenarioURL("mielec"), "mielec_drt_config.xml");
-		Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), new DvrpConfigGroup(),
+		Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), dvrpConfig,
 				new OTFVisConfigGroup());
 
 		config.controller().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists);
@@ -305,11 +332,11 @@ public void install() {
 		controller.run();
 
 		var expectedStats = Stats.newBuilder()
-				.rejectionRate(0.04)
-				.rejections(16)
-				.waitAverage(278.92)
-				.inVehicleTravelTimeMean(384.6)
-				.totalTravelTimeMean(663.52)
+				.rejectionRate(0.05)
+				.rejections(18)
+				.waitAverage(276.95)
+				.inVehicleTravelTimeMean(384.72)
+				.totalTravelTimeMean(661.66)
 				.build();
 
 		verifyDrtCustomerStatsCloseToExpectedStats(utils.getOutputDirectory(), expectedStats);
@@ -318,10 +345,15 @@ public void install() {
 	@Test
 	void testRunDrtWithPrebooking() {
 		Id.resetCaches();
+
+		DvrpConfigGroup dvrpConfigGroup = new DvrpConfigGroup();
+		DvrpTravelTimeMatrixParams matrixParams = dvrpConfigGroup.getTravelTimeMatrixParams();
+		matrixParams.addParameterSet(matrixParams.createParameterSet(SquareGridZoneSystemParams.SET_NAME));
+
 		URL configUrl = IOUtils.extendUrl(ExamplesUtils.getTestScenarioURL("mielec"),
 				"mielec_drt_config.xml");
 
-		Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), new DvrpConfigGroup(),
+		Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), dvrpConfigGroup,
 				new OTFVisConfigGroup());
 
 		config.controller().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists);
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/router/DvrpModeRoutingNetworkModule.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/router/DvrpModeRoutingNetworkModule.java
index fbca29c75b1..42f9267f7a3 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/router/DvrpModeRoutingNetworkModule.java
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/router/DvrpModeRoutingNetworkModule.java
@@ -24,8 +24,11 @@
 import java.util.Set;
 
 import org.matsim.api.core.v01.network.Network;
+import org.matsim.contrib.common.zones.ZoneSystem;
+import org.matsim.contrib.common.zones.ZoneSystemUtils;
 import org.matsim.contrib.dvrp.run.AbstractDvrpModeModule;
 import org.matsim.contrib.dvrp.run.DvrpConfigGroup;
+import org.matsim.contrib.zone.skims.DvrpTravelTimeMatrixParams;
 import org.matsim.contrib.zone.skims.FreeSpeedTravelTimeMatrix;
 import org.matsim.contrib.zone.skims.TravelTimeMatrix;
 import org.matsim.core.config.Config;
@@ -78,9 +81,15 @@ public void install() {
 			//use mode-specific travel time matrix built for this subnetwork
 			//lazily initialised: optimisers may not need it
 			bindModal(TravelTimeMatrix.class).toProvider(modalProvider(
-					getter -> FreeSpeedTravelTimeMatrix.createFreeSpeedMatrix(getter.getModal(Network.class),
-							dvrpConfigGroup.getTravelTimeMatrixParams(), globalConfigGroup.getNumberOfThreads(),
-							qSimConfigGroup.getTimeStepSize()))).in(Singleton.class);
+					getter -> {
+						Network network = getter.getModal(Network.class);
+						DvrpTravelTimeMatrixParams matrixParams = dvrpConfigGroup.getTravelTimeMatrixParams();
+						ZoneSystem zoneSystem = ZoneSystemUtils.createZoneSystem(getConfig().getContext(), network,
+							matrixParams.getZoneSystemParams(), getConfig().global().getCoordinateSystem());
+						return FreeSpeedTravelTimeMatrix.createFreeSpeedMatrix(network, zoneSystem,
+							matrixParams, globalConfigGroup.getNumberOfThreads(),
+                                qSimConfigGroup.getTimeStepSize());
+                    })).in(Singleton.class);
 		} else {
 			//use DVRP-routing (dvrp-global) network
 			bindModal(Network.class).to(
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/run/DvrpConfigGroup.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/run/DvrpConfigGroup.java
index 5724d537d36..25cc051ece8 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/run/DvrpConfigGroup.java
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/run/DvrpConfigGroup.java
@@ -27,7 +27,7 @@
 import org.apache.logging.log4j.Logger;
 import org.matsim.api.core.v01.TransportMode;
 import org.matsim.contrib.dynagent.run.DynQSimConfigConsistencyChecker;
-import org.matsim.contrib.util.ReflectiveConfigGroupWithConfigurableParameterSets;
+import org.matsim.contrib.common.util.ReflectiveConfigGroupWithConfigurableParameterSets;
 import org.matsim.contrib.zone.skims.DvrpTravelTimeMatrixParams;
 import org.matsim.core.config.Config;
 
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/run/DvrpModule.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/run/DvrpModule.java
index 536f9315c82..44da59b0388 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/run/DvrpModule.java
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/run/DvrpModule.java
@@ -22,12 +22,15 @@
 import jakarta.inject.Provider;
 
 import org.matsim.api.core.v01.network.Network;
+import org.matsim.contrib.common.zones.ZoneSystem;
+import org.matsim.contrib.common.zones.ZoneSystemUtils;
 import org.matsim.contrib.dvrp.fleet.DvrpVehicleLookup;
 import org.matsim.contrib.dvrp.passenger.PassengerModule;
 import org.matsim.contrib.dvrp.router.DvrpGlobalRoutingNetworkProvider;
 import org.matsim.contrib.dvrp.trafficmonitoring.DvrpTravelTimeModule;
 import org.matsim.contrib.dvrp.vrpagent.VrpAgentQueryHelper;
 import org.matsim.contrib.dynagent.run.DynActivityEngine;
+import org.matsim.contrib.zone.skims.DvrpTravelTimeMatrixParams;
 import org.matsim.contrib.zone.skims.FreeSpeedTravelTimeMatrix;
 import org.matsim.contrib.zone.skims.TravelTimeMatrix;
 import org.matsim.core.config.groups.QSimConfigGroup;
@@ -84,7 +87,10 @@ public void install() {
 			public TravelTimeMatrix get() {
 				var numberOfThreads = getConfig().global().getNumberOfThreads();
 				var params = dvrpConfigGroup.getTravelTimeMatrixParams();
-				return FreeSpeedTravelTimeMatrix.createFreeSpeedMatrix(network, params, numberOfThreads,
+				DvrpTravelTimeMatrixParams matrixParams = dvrpConfigGroup.getTravelTimeMatrixParams();
+				ZoneSystem zoneSystem = ZoneSystemUtils.createZoneSystem(getConfig().getContext(), network,
+					matrixParams.getZoneSystemParams(), getConfig().global().getCoordinateSystem());
+				return FreeSpeedTravelTimeMatrix.createFreeSpeedMatrix(network, zoneSystem, params, numberOfThreads,
 						qSimConfigGroup.getTimeStepSize());
 			}
 		}).in(Singleton.class);
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/SquareGrid.java b/contribs/dvrp/src/main/java/org/matsim/contrib/zone/SquareGrid.java
deleted file mode 100644
index 6ca95e174d0..00000000000
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/SquareGrid.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/* *********************************************************************** *
- * project: org.matsim.*
- *                                                                         *
- * *********************************************************************** *
- *                                                                         *
- * copyright       : (C) 2016 by the members listed in the COPYING,        *
- *                   LICENSE and WARRANTY file.                            *
- * email           : info at matsim dot org                                *
- *                                                                         *
- * *********************************************************************** *
- *                                                                         *
- *   This program is free software; you can redistribute it and/or modify  *
- *   it under the terms of the GNU General Public License as published by  *
- *   the Free Software Foundation; either version 2 of the License, or     *
- *   (at your option) any later version.                                   *
- *   See also COPYING, LICENSE and WARRANTY file                           *
- *                                                                         *
- * *********************************************************************** */
-
-package org.matsim.contrib.zone;
-
-import java.util.Collection;
-
-import org.matsim.api.core.v01.Coord;
-import org.matsim.api.core.v01.Id;
-import org.matsim.api.core.v01.network.Node;
-
-import com.google.common.base.Preconditions;
-
-public class SquareGrid {
-	public static final double EPSILON = 1;
-
-	private final double cellSize;
-
-	private double minX;
-	private double minY;
-	private double maxX;
-	private double maxY;
-
-	private final int cols;
-	private final int rows;
-
-	private Zone[] zones;
-
-	public SquareGrid(Collection<? extends Node> nodes, double cellSize) {
-		Preconditions.checkArgument(!nodes.isEmpty(), "Cannot create SquareGrid if no nodes");
-		this.cellSize = cellSize;
-
-		initBounds(nodes);
-
-		cols = (int)Math.ceil((maxX - minX) / cellSize);
-		rows = (int)Math.ceil((maxY - minY) / cellSize);
-		zones = new Zone[rows * cols];
-	}
-
-	// This method's content has been copied from NetworkImpl
-	private void initBounds(Collection<? extends Node> nodes) {
-		minX = Double.POSITIVE_INFINITY;
-		minY = Double.POSITIVE_INFINITY;
-		maxX = Double.NEGATIVE_INFINITY;
-		maxY = Double.NEGATIVE_INFINITY;
-		for (Node n : nodes) {
-			if (n.getCoord().getX() < minX) {
-				minX = n.getCoord().getX();
-			}
-			if (n.getCoord().getY() < minY) {
-				minY = n.getCoord().getY();
-			}
-			if (n.getCoord().getX() > maxX) {
-				maxX = n.getCoord().getX();
-			}
-			if (n.getCoord().getY() > maxY) {
-				maxY = n.getCoord().getY();
-			}
-		}
-		minX -= EPSILON;
-		minY -= EPSILON;
-		maxX += EPSILON;
-		maxY += EPSILON;
-		// yy the above four lines are problematic if the coordinate values are much smaller than one. kai, oct'15
-	}
-
-	public Zone getZone(Coord coord) {
-		return zones[getIndex(coord)];
-	}
-
-	public Zone getOrCreateZone(Coord coord) {
-		int index = getIndex(coord);
-		Zone zone = zones[index];
-		if (zone == null) {
-			double x0 = minX + cellSize / 2;
-			double y0 = minY + cellSize / 2;
-			int r = bin(coord.getY(), minY);
-			int c = bin(coord.getX(), minX);
-			Coord centroid = new Coord(c * cellSize + x0, r * cellSize + y0);
-			zone = new Zone(Id.create(index, Zone.class), "square", centroid);
-			zones[index] = zone;
-		}
-		return zone;
-	}
-
-	private int getIndex(Coord coord) {
-		Preconditions.checkArgument(coord.getX() >= minX, "Coord.x less than minX");
-		Preconditions.checkArgument(coord.getX() <= maxX, "Coord.x greater than maxX");
-		Preconditions.checkArgument(coord.getY() >= minY, "Coord.y less than minY");
-		Preconditions.checkArgument(coord.getY() <= maxY, "Coord.y greater than maxY");
-		int r = bin(coord.getY(), minY);
-		int c = bin(coord.getX(), minX);
-		return r * cols + c;
-	}
-
-	private int bin(double coord, double minCoord) {
-		return (int)((coord - minCoord) / cellSize);
-	}
-}
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/SquareGridSystem.java b/contribs/dvrp/src/main/java/org/matsim/contrib/zone/SquareGridSystem.java
deleted file mode 100644
index 1a4825a9470..00000000000
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/SquareGridSystem.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/* *********************************************************************** *
- * project: org.matsim.*
- *                                                                         *
- * *********************************************************************** *
- *                                                                         *
- * copyright       : (C) 2015 by the members listed in the COPYING,        *
- *                   LICENSE and WARRANTY file.                            *
- * email           : info at matsim dot org                                *
- *                                                                         *
- * *********************************************************************** *
- *                                                                         *
- *   This program is free software; you can redistribute it and/or modify  *
- *   it under the terms of the GNU General Public License as published by  *
- *   the Free Software Foundation; either version 2 of the License, or     *
- *   (at your option) any later version.                                   *
- *   See also COPYING, LICENSE and WARRANTY file                           *
- *                                                                         *
- * *********************************************************************** */
-
-package org.matsim.contrib.zone;
-
-import static java.util.stream.Collectors.toMap;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Map;
-
-import org.matsim.api.core.v01.Id;
-import org.matsim.api.core.v01.network.Node;
-
-public class SquareGridSystem implements ZonalSystem {
-	private final SquareGrid grid;
-	private final Map<Id<Zone>, Zone> zones;
-
-	public SquareGridSystem(Collection<? extends Node> nodes, double cellSize) {
-		this.grid = new SquareGrid(nodes, cellSize);
-		zones = nodes.stream()
-				.map(n -> grid.getOrCreateZone(n.getCoord()))
-				.collect(toMap(Zone::getId, z -> z, (z1, z2) -> z1));
-	}
-
-	@Override
-	public Map<Id<Zone>, Zone> getZones() {
-		return Collections.unmodifiableMap(zones);
-	}
-
-	@Override
-	public Zone getZone(Node node) {
-		return grid.getZone(node.getCoord());
-	}
-}
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/ZonalSystem.java b/contribs/dvrp/src/main/java/org/matsim/contrib/zone/ZonalSystem.java
deleted file mode 100644
index d94238175c4..00000000000
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/ZonalSystem.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/* *********************************************************************** *
- * project: org.matsim.*
- *                                                                         *
- * *********************************************************************** *
- *                                                                         *
- * copyright       : (C) 2015 by the members listed in the COPYING,        *
- *                   LICENSE and WARRANTY file.                            *
- * email           : info at matsim dot org                                *
- *                                                                         *
- * *********************************************************************** *
- *                                                                         *
- *   This program is free software; you can redistribute it and/or modify  *
- *   it under the terms of the GNU General Public License as published by  *
- *   the Free Software Foundation; either version 2 of the License, or     *
- *   (at your option) any later version.                                   *
- *   See also COPYING, LICENSE and WARRANTY file                           *
- *                                                                         *
- * *********************************************************************** */
-
-package org.matsim.contrib.zone;
-
-import java.util.Map;
-
-import org.matsim.api.core.v01.Id;
-import org.matsim.api.core.v01.network.Node;
-
-public interface ZonalSystem {
-	Map<Id<Zone>, Zone> getZones();
-
-	Zone getZone(Node node);
-}
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/ZonalSystemImpl.java b/contribs/dvrp/src/main/java/org/matsim/contrib/zone/ZonalSystemImpl.java
deleted file mode 100644
index c24d789f197..00000000000
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/ZonalSystemImpl.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/* *********************************************************************** *
- * project: org.matsim.*
- *                                                                         *
- * *********************************************************************** *
- *                                                                         *
- * copyright       : (C) 2016 by the members listed in the COPYING,        *
- *                   LICENSE and WARRANTY file.                            *
- * email           : info at matsim dot org                                *
- *                                                                         *
- * *********************************************************************** *
- *                                                                         *
- *   This program is free software; you can redistribute it and/or modify  *
- *   it under the terms of the GNU General Public License as published by  *
- *   the Free Software Foundation; either version 2 of the License, or     *
- *   (at your option) any later version.                                   *
- *   See also COPYING, LICENSE and WARRANTY file                           *
- *                                                                         *
- * *********************************************************************** */
-
-package org.matsim.contrib.zone;
-
-import java.util.Map;
-
-import org.matsim.api.core.v01.Id;
-import org.matsim.api.core.v01.network.Network;
-import org.matsim.api.core.v01.network.Node;
-import org.matsim.contrib.zone.util.NetworkWithZonesUtils;
-import org.matsim.contrib.zone.util.ZoneFinder;
-
-public class ZonalSystemImpl implements ZonalSystem {
-	private final Map<Id<Zone>, Zone> zones;
-	private final Map<Id<Node>, Zone> nodeToZoneMap;
-
-	public ZonalSystemImpl(Map<Id<Zone>, Zone> zones, ZoneFinder zoneFinder, Network network) {
-		this.zones = zones;
-		nodeToZoneMap = NetworkWithZonesUtils.createNodeToZoneMap(network, zoneFinder);
-	}
-
-	@Override
-	public Map<Id<Zone>, Zone> getZones() {
-		return zones;
-	}
-
-	@Override
-	public Zone getZone(Node node) {
-		return nodeToZoneMap.get(node.getId());
-	}
-}
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/ZonalSystems.java b/contribs/dvrp/src/main/java/org/matsim/contrib/zone/ZonalSystems.java
deleted file mode 100644
index 4ec41a37a47..00000000000
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/ZonalSystems.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/* *********************************************************************** *
- * project: org.matsim.*
- *                                                                         *
- * *********************************************************************** *
- *                                                                         *
- * copyright       : (C) 2015 by the members listed in the COPYING,        *
- *                   LICENSE and WARRANTY file.                            *
- * email           : info at matsim dot org                                *
- *                                                                         *
- * *********************************************************************** *
- *                                                                         *
- *   This program is free software; you can redistribute it and/or modify  *
- *   it under the terms of the GNU General Public License as published by  *
- *   the Free Software Foundation; either version 2 of the License, or     *
- *   (at your option) any later version.                                   *
- *   See also COPYING, LICENSE and WARRANTY file                           *
- *                                                                         *
- * *********************************************************************** */
-
-package org.matsim.contrib.zone;
-
-import static java.util.stream.Collectors.*;
-
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.BinaryOperator;
-import java.util.stream.Collectors;
-
-import org.apache.commons.lang3.tuple.Pair;
-import org.locationtech.jts.geom.Point;
-import org.locationtech.jts.geom.prep.PreparedGeometry;
-import org.matsim.api.core.v01.Id;
-import org.matsim.api.core.v01.IdMap;
-import org.matsim.api.core.v01.network.Node;
-import org.matsim.contrib.common.util.DistanceUtils;
-import org.matsim.core.utils.geometry.geotools.MGC;
-
-import com.google.common.collect.Maps;
-
-//TODO add zone indexing?
-public class ZonalSystems {
-	public static Set<Zone> filterZonesWithNodes(Collection<? extends Node> nodes, ZonalSystem zonalSystem) {
-		return nodes.stream().map(zonalSystem::getZone).collect(toSet());
-	}
-
-	public static List<Node> selectNodesWithinArea(Collection<? extends Node> nodes, List<PreparedGeometry> areaGeoms) {
-		return nodes.stream().filter(node -> {
-			Point point = MGC.coord2Point(node.getCoord());
-			return areaGeoms.stream().anyMatch(serviceArea -> serviceArea.intersects(point));
-		}).collect(toList());
-	}
-
-	public static Map<Zone, Node> computeMostCentralNodes(Collection<? extends Node> nodes, ZonalSystem zonalSystem) {
-		BinaryOperator<Node> chooseMoreCentralNode = (n1, n2) -> {
-			Zone zone = zonalSystem.getZone(n1);
-			return DistanceUtils.calculateSquaredDistance(n1, zone) <= DistanceUtils.calculateSquaredDistance(n2,
-					zone) ? n1 : n2;
-		};
-		return nodes.stream()
-				.map(n -> Pair.of(n, zonalSystem.getZone(n)))
-				.collect(toMap(Pair::getValue, Pair::getKey, chooseMoreCentralNode));
-	}
-
-	public static IdMap<Zone, List<Zone>> initZonesByDistance(Map<Id<Zone>, Zone> zones) {
-		IdMap<Zone, List<Zone>> zonesByDistance = new IdMap<>(Zone.class);
-		for (final Zone currentZone : zones.values()) {
-			List<Zone> sortedZones = zones.values()
-					.stream()
-					.sorted(Comparator.comparing(z -> DistanceUtils.calculateSquaredDistance(currentZone, z)))
-					.collect(Collectors.toList());
-			zonesByDistance.put(currentZone.getId(), sortedZones);
-		}
-		return zonesByDistance;
-	}
-}
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/Zone.java b/contribs/dvrp/src/main/java/org/matsim/contrib/zone/Zone.java
deleted file mode 100644
index c2e548b1e29..00000000000
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/Zone.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/* *********************************************************************** *
- * project: org.matsim.*
- *                                                                         *
- * *********************************************************************** *
- *                                                                         *
- * copyright       : (C) 2012 by the members listed in the COPYING,        *
- *                   LICENSE and WARRANTY file.                            *
- * email           : info at matsim dot org                                *
- *                                                                         *
- * *********************************************************************** *
- *                                                                         *
- *   This program is free software; you can redistribute it and/or modify  *
- *   it under the terms of the GNU General Public License as published by  *
- *   the Free Software Foundation; either version 2 of the License, or     *
- *   (at your option) any later version.                                   *
- *   See also COPYING, LICENSE and WARRANTY file                           *
- *                                                                         *
- * *********************************************************************** */
-
-package org.matsim.contrib.zone;
-
-import org.locationtech.jts.geom.MultiPolygon;
-import org.matsim.api.core.v01.BasicLocation;
-import org.matsim.api.core.v01.Coord;
-import org.matsim.api.core.v01.Id;
-import org.matsim.api.core.v01.Identifiable;
-import org.matsim.core.utils.geometry.geotools.MGC;
-
-public class Zone implements BasicLocation, Identifiable<Zone> {
-	private final Id<Zone> id;
-	private final String type;
-
-	private MultiPolygon multiPolygon;
-	private Coord centroid;
-
-	public Zone(Id<Zone> id, String type) {
-		this.id = id;
-		this.type = type;
-	}
-
-	public Zone(Id<Zone> id, String type, Coord centroid) {
-		this.id = id;
-		this.type = type;
-		this.centroid = centroid;
-	}
-
-	public Zone(Id<Zone> id, String type, MultiPolygon multiPolygon) {
-		this.id = id;
-		this.type = type;
-
-		this.multiPolygon = multiPolygon;
-		centroid = MGC.point2Coord(multiPolygon.getCentroid());
-	}
-
-	@Override
-	public Id<Zone> getId() {
-		return id;
-	}
-
-	@Override
-	public Coord getCoord() {
-		return centroid;
-	}
-
-	public void setCoord(Coord coord) {
-		this.centroid = coord;
-	}
-
-	public String getType() {
-		return type;
-	}
-
-	public MultiPolygon getMultiPolygon() {
-		return multiPolygon;
-	}
-
-	public void setMultiPolygon(MultiPolygon multiPolygon) {
-		this.multiPolygon = multiPolygon;
-		centroid = MGC.point2Coord(multiPolygon.getCentroid());
-	}
-}
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/Zones.java b/contribs/dvrp/src/main/java/org/matsim/contrib/zone/Zones.java
deleted file mode 100644
index 34321d05068..00000000000
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/Zones.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/* *********************************************************************** *
- * project: org.matsim.*
- *                                                                         *
- * *********************************************************************** *
- *                                                                         *
- * copyright       : (C) 2014 by the members listed in the COPYING,        *
- *                   LICENSE and WARRANTY file.                            *
- * email           : info at matsim dot org                                *
- *                                                                         *
- * *********************************************************************** *
- *                                                                         *
- *   This program is free software; you can redistribute it and/or modify  *
- *   it under the terms of the GNU General Public License as published by  *
- *   the Free Software Foundation; either version 2 of the License, or     *
- *   (at your option) any later version.                                   *
- *   See also COPYING, LICENSE and WARRANTY file                           *
- *                                                                         *
- * *********************************************************************** */
-
-package org.matsim.contrib.zone;
-
-import java.io.File;
-import java.io.UncheckedIOException;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Map;
-
-import org.matsim.api.core.v01.Id;
-import org.matsim.contrib.zone.io.ZoneShpReader;
-import org.matsim.contrib.zone.io.ZoneXmlReader;
-
-public class Zones {
-	public static Map<Id<Zone>, Zone> readZones(String zonesXmlFile, String zonesShpFile) {
-		try {
-			return readZones(new File(zonesXmlFile).toURI().toURL(), new File(zonesShpFile).toURI().toURL());
-		} catch (MalformedURLException e) {
-			throw new UncheckedIOException(e);
-		}
-	}
-
-	public static Map<Id<Zone>, Zone> readZones(URL zonesXmlUrl, URL zonesShpUrl) {
-		ZoneXmlReader xmlReader = new ZoneXmlReader();
-		xmlReader.readURL(zonesXmlUrl);
-		Map<Id<Zone>, Zone> zones = xmlReader.getZones();
-
-		ZoneShpReader shpReader = new ZoneShpReader(zones);
-		shpReader.readZones(zonesShpUrl);
-		return zones;
-	}
-}
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/AdaptiveTravelTimeMatrixImpl.java b/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/AdaptiveTravelTimeMatrixImpl.java
index 987f29ea47a..08f899c9d2a 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/AdaptiveTravelTimeMatrixImpl.java
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/AdaptiveTravelTimeMatrixImpl.java
@@ -22,9 +22,9 @@
 import org.matsim.api.core.v01.network.Network;
 import org.matsim.api.core.v01.network.Node;
 import org.matsim.contrib.common.util.DistanceUtils;
-import org.matsim.contrib.zone.SquareGridSystem;
-import org.matsim.contrib.zone.ZonalSystems;
-import org.matsim.contrib.zone.Zone;
+import org.matsim.contrib.common.zones.Zone;
+import org.matsim.contrib.common.zones.ZoneSystem;
+import org.matsim.contrib.common.zones.ZoneSystemUtils;
 
 import java.util.List;
 import java.util.Map;
@@ -38,19 +38,19 @@
 public class AdaptiveTravelTimeMatrixImpl implements AdaptiveTravelTimeMatrix {
 	private final double TIME_INTERVAL = 3600.;
 	private final List<Matrix> timeDependentMatrix;
-	private final SquareGridSystem gridSystem;
+	private final ZoneSystem gridSystem;
 	private final double alpha;
 	private final Map<Zone, Node> centralNodes;
 	private final int numberOfBins;
 	private final DvrpTravelTimeMatrixParams params;
 	private final Map<SparseTravelTimeKey, Double> sparseTravelTimeCache = new ConcurrentHashMap<>();
 
-	public AdaptiveTravelTimeMatrixImpl(double maxTime, Network dvrpNetwork, DvrpTravelTimeMatrixParams params,
+	public AdaptiveTravelTimeMatrixImpl(double maxTime, Network dvrpNetwork, ZoneSystem zoneSystem, DvrpTravelTimeMatrixParams params,
 										TravelTimeMatrix freeSpeedMatrix, double alpha) {
 		this.alpha = alpha;
 		this.numberOfBins = numberOfBins(maxTime);
-		this.gridSystem = new SquareGridSystem(dvrpNetwork.getNodes().values(), params.cellSize);
-		this.centralNodes = ZonalSystems.computeMostCentralNodes(dvrpNetwork.getNodes().values(), this.gridSystem);
+		this.gridSystem = zoneSystem;
+		this.centralNodes = ZoneSystemUtils.computeMostCentralNodes(dvrpNetwork.getNodes().values(), this.gridSystem);
 		this.timeDependentMatrix = IntStream.range(0, numberOfBins).mapToObj(i -> new Matrix(centralNodes.keySet()))
 				.toList();
 		this.params = params;
@@ -113,7 +113,7 @@ public double getTravelTime(Node fromNode, Node toNode, double departureTime) {
 		if (sparseValue != null) {
 			return sparseValue;
 		}
-		return this.timeDependentMatrix.get(bin).get(this.gridSystem.getZone(fromNode), this.gridSystem.getZone(toNode));
+		return this.timeDependentMatrix.get(bin).get(this.gridSystem.getZoneForNodeId(fromNode.getId()).orElseThrow(), this.gridSystem.getZoneForNodeId(toNode.getId()).orElseThrow());
 	}
 
 	int getBin(double departureTime) {
@@ -135,7 +135,7 @@ public void setTravelTime(Node fromNode, Node toNode, double routeEstimate, doub
 		} else {
 			double currentTravelTimeEstimate = this.getTravelTime(fromNode, toNode, departureTime);
 			double value = getUpdatedValue(currentTravelTimeEstimate, routeEstimate, this.alpha);
-			this.timeDependentMatrix.get(bin).set(this.gridSystem.getZone(fromNode), this.gridSystem.getZone(toNode),
+			this.timeDependentMatrix.get(bin).set(this.gridSystem.getZoneForNodeId(fromNode.getId()).orElseThrow(), this.gridSystem.getZoneForNodeId(toNode.getId()).orElseThrow(),
 					value);
 		}
 
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/AdaptiveTravelTimeMatrixModule.java b/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/AdaptiveTravelTimeMatrixModule.java
index a546f83a429..d0bf361fd40 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/AdaptiveTravelTimeMatrixModule.java
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/AdaptiveTravelTimeMatrixModule.java
@@ -22,6 +22,8 @@
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import org.matsim.api.core.v01.network.Network;
+import org.matsim.contrib.common.zones.ZoneSystem;
+import org.matsim.contrib.common.zones.ZoneSystemUtils;
 import org.matsim.contrib.dvrp.run.AbstractDvrpModeModule;
 import org.matsim.contrib.dvrp.run.DvrpConfigGroup;
 import org.matsim.core.config.groups.QSimConfigGroup;
@@ -46,10 +48,17 @@ public AdaptiveTravelTimeMatrixModule(String mode) {
     @Override
     public void install() {
         bindModal(AdaptiveTravelTimeMatrix.class).toProvider(modalProvider(
-                getter -> new AdaptiveTravelTimeMatrixImpl(qsimConfig.getEndTime().orElse(ALTERNATIVE_ENDTIME),
-                        getter.getModal(Network.class),
-                        dvrpConfigGroup.getTravelTimeMatrixParams(),
-                        getter.getModal(TravelTimeMatrix.class),SMOOTHING_ALPHA)))
+                getter -> {
+					Network network = getter.getModal(Network.class);
+					DvrpTravelTimeMatrixParams matrixParams = dvrpConfigGroup.getTravelTimeMatrixParams();
+					ZoneSystem zoneSystem = ZoneSystemUtils.createZoneSystem(getConfig().getContext(), network,
+						matrixParams.getZoneSystemParams(), getConfig().global().getCoordinateSystem());
+                    return new AdaptiveTravelTimeMatrixImpl(qsimConfig.getEndTime().orElse(ALTERNATIVE_ENDTIME),
+                            network,
+							zoneSystem,
+                            matrixParams,
+                            getter.getModal(TravelTimeMatrix.class), SMOOTHING_ALPHA);
+                }))
                 .in(Singleton.class);
     }
 }
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/DvrpTravelTimeMatrixParams.java b/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/DvrpTravelTimeMatrixParams.java
index 49fa4f29eb1..1c95a88432f 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/DvrpTravelTimeMatrixParams.java
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/DvrpTravelTimeMatrixParams.java
@@ -20,23 +20,19 @@
 
 package org.matsim.contrib.zone.skims;
 
-import org.matsim.core.config.ConfigGroup;
-import org.matsim.core.config.ReflectiveConfigGroup;
-
-import jakarta.validation.constraints.Positive;
 import jakarta.validation.constraints.PositiveOrZero;
+import org.matsim.contrib.common.util.ReflectiveConfigGroupWithConfigurableParameterSets;
+import org.matsim.contrib.common.zones.ZoneSystemParams;
+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.square.SquareGridZoneSystemParams;
 
 /**
  * @author Michal Maciejewski (michalm)
  */
-public class DvrpTravelTimeMatrixParams extends ReflectiveConfigGroup {
+public class DvrpTravelTimeMatrixParams extends ReflectiveConfigGroupWithConfigurableParameterSets {
 	public static final String SET_NAME = "travelTimeMatrix";
 
-	@Parameter
-	@Comment("size of square cells (meters) used for computing travel time matrix." + " Default value is 200 m")
-	@Positive
-	public double cellSize = 200; //[m]
-
 	// Satisfying only one criterion (max distance or travel time) is enough to be considered a neighbour.
 
 	@Parameter
@@ -58,13 +54,32 @@ public class DvrpTravelTimeMatrixParams extends ReflectiveConfigGroup {
 			+ " The unit is seconds. Default value is 0 s (for backward compatibility).")
 	@PositiveOrZero
 	public double maxNeighborTravelTime = 0; //[s]
+	private ZoneSystemParams zoneSystemParams;
+
 
 	public DvrpTravelTimeMatrixParams() {
 		super(SET_NAME);
+		initSingletonParameterSets();
+	}
+
+
+	private void initSingletonParameterSets() {
+
+		//insertion search params (one of: extensive, selective, repeated selective)
+		addDefinition(SquareGridZoneSystemParams.SET_NAME, SquareGridZoneSystemParams::new,
+			() -> zoneSystemParams,
+			params -> zoneSystemParams = (SquareGridZoneSystemParams)params);
+
+		addDefinition(GISFileZoneSystemParams.SET_NAME, GISFileZoneSystemParams::new,
+			() -> zoneSystemParams,
+			params -> zoneSystemParams = (GISFileZoneSystemParams)params);
+
+		addDefinition(H3GridZoneSystemParams.SET_NAME, H3GridZoneSystemParams::new,
+			() -> zoneSystemParams,
+			params -> zoneSystemParams = (H3GridZoneSystemParams)params);
 	}
 
-	@Override
-	public ConfigGroup createParameterSet(String type) {
-		return super.createParameterSet(type);
+	public ZoneSystemParams getZoneSystemParams() {
+		return zoneSystemParams;
 	}
 }
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/FreeSpeedTravelTimeMatrix.java b/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/FreeSpeedTravelTimeMatrix.java
index 0adab8701df..6351dd3bce8 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/FreeSpeedTravelTimeMatrix.java
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/FreeSpeedTravelTimeMatrix.java
@@ -22,28 +22,28 @@
 
 import org.matsim.api.core.v01.network.Network;
 import org.matsim.api.core.v01.network.Node;
+import org.matsim.contrib.common.zones.ZoneSystem;
+import org.matsim.contrib.common.zones.ZoneSystemUtils;
 import org.matsim.contrib.dvrp.router.TimeAsTravelDisutility;
 import org.matsim.contrib.dvrp.trafficmonitoring.QSimFreeSpeedTravelTime;
-import org.matsim.contrib.zone.SquareGridSystem;
-import org.matsim.contrib.zone.ZonalSystems;
 import org.matsim.core.router.util.TravelTime;
 
 /**
  * @author Michal Maciejewski (michalm)
  */
 public class FreeSpeedTravelTimeMatrix implements TravelTimeMatrix {
-	public static FreeSpeedTravelTimeMatrix createFreeSpeedMatrix(Network dvrpNetwork, DvrpTravelTimeMatrixParams params, int numberOfThreads,
+	public static FreeSpeedTravelTimeMatrix createFreeSpeedMatrix(Network dvrpNetwork, ZoneSystem zoneSystem, DvrpTravelTimeMatrixParams params, int numberOfThreads,
 		double qSimTimeStepSize) {
-		return new FreeSpeedTravelTimeMatrix(dvrpNetwork, params, numberOfThreads, new QSimFreeSpeedTravelTime(qSimTimeStepSize));
+		return new FreeSpeedTravelTimeMatrix(dvrpNetwork, zoneSystem, params, numberOfThreads, new QSimFreeSpeedTravelTime(qSimTimeStepSize));
 	}
 
-	private final SquareGridSystem gridSystem;
+	private final ZoneSystem zoneSystem;
 	private final Matrix freeSpeedTravelTimeMatrix;
 	private final SparseMatrix freeSpeedTravelTimeSparseMatrix;
 
-	public FreeSpeedTravelTimeMatrix(Network dvrpNetwork, DvrpTravelTimeMatrixParams params, int numberOfThreads, TravelTime travelTime) {
-		gridSystem = new SquareGridSystem(dvrpNetwork.getNodes().values(), params.cellSize);
-		var centralNodes = ZonalSystems.computeMostCentralNodes(dvrpNetwork.getNodes().values(), gridSystem);
+	public FreeSpeedTravelTimeMatrix(Network dvrpNetwork, ZoneSystem zoneSystem, DvrpTravelTimeMatrixParams params, int numberOfThreads, TravelTime travelTime) {
+		this.zoneSystem = zoneSystem;
+		var centralNodes = ZoneSystemUtils.computeMostCentralNodes(dvrpNetwork.getNodes().values(), zoneSystem);
 		var travelDisutility = new TimeAsTravelDisutility(travelTime);
 		var routingParams = new TravelTimeMatrices.RoutingParams(dvrpNetwork, travelTime, travelDisutility, numberOfThreads);
 		freeSpeedTravelTimeMatrix = TravelTimeMatrices.calculateTravelTimeMatrix(routingParams, centralNodes, 0);
@@ -62,10 +62,10 @@ public int getTravelTime(Node fromNode, Node toNode, double departureTime) {
 				return time;
 			}
 		}
-		return freeSpeedTravelTimeMatrix.get(gridSystem.getZone(fromNode), gridSystem.getZone(toNode));
+		return freeSpeedTravelTimeMatrix.get(zoneSystem.getZoneForNodeId(fromNode.getId()).orElseThrow(), zoneSystem.getZoneForNodeId(toNode.getId()).orElseThrow());
 	}
 
 	public int getZonalTravelTime(Node fromNode, Node toNode, double departureTime) {
-		return freeSpeedTravelTimeMatrix.get(gridSystem.getZone(fromNode), gridSystem.getZone(toNode));
+		return freeSpeedTravelTimeMatrix.get(zoneSystem.getZoneForNodeId(fromNode.getId()).orElseThrow(), zoneSystem.getZoneForNodeId(toNode.getId()).orElseThrow());
 	}
 }
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/Matrix.java b/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/Matrix.java
index d497276c3f5..96425c78ef5 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/Matrix.java
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/Matrix.java
@@ -27,7 +27,7 @@
 import java.util.Set;
 
 import org.matsim.api.core.v01.Id;
-import org.matsim.contrib.zone.Zone;
+import org.matsim.contrib.common.zones.Zone;
 
 /**
  * Based on FloatMatrix from sbb-matsim-extensions
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/TravelTimeMatrices.java b/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/TravelTimeMatrices.java
index dcea254ac6c..7caf007d369 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/TravelTimeMatrices.java
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/TravelTimeMatrices.java
@@ -28,8 +28,8 @@
 
 import org.matsim.api.core.v01.network.Network;
 import org.matsim.api.core.v01.network.Node;
+import org.matsim.contrib.common.zones.Zone;
 import org.matsim.contrib.util.ExecutorServiceWithResource;
-import org.matsim.contrib.zone.Zone;
 import org.matsim.contrib.zone.skims.SparseMatrix.NodeAndTime;
 import org.matsim.contrib.zone.skims.SparseMatrix.SparseRow;
 import org.matsim.core.router.speedy.LeastCostPathTree;
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/util/NetworkWithZonesUtils.java b/contribs/dvrp/src/main/java/org/matsim/contrib/zone/util/NetworkWithZonesUtils.java
deleted file mode 100644
index 0a56d2abffc..00000000000
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/util/NetworkWithZonesUtils.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/* *********************************************************************** *
- * project: org.matsim.*
- *                                                                         *
- * *********************************************************************** *
- *                                                                         *
- * copyright       : (C) 2015 by the members listed in the COPYING,        *
- *                   LICENSE and WARRANTY file.                            *
- * email           : info at matsim dot org                                *
- *                                                                         *
- * *********************************************************************** *
- *                                                                         *
- *   This program is free software; you can redistribute it and/or modify  *
- *   it under the terms of the GNU General Public License as published by  *
- *   the Free Software Foundation; either version 2 of the License, or     *
- *   (at your option) any later version.                                   *
- *   See also COPYING, LICENSE and WARRANTY file                           *
- *                                                                         *
- * *********************************************************************** */
-
-package org.matsim.contrib.zone.util;
-
-import org.matsim.api.core.v01.IdCollectors;
-import org.matsim.api.core.v01.IdMap;
-import org.matsim.api.core.v01.Identifiable;
-import org.matsim.api.core.v01.network.Link;
-import org.matsim.api.core.v01.network.Network;
-import org.matsim.api.core.v01.network.Node;
-import org.matsim.contrib.zone.Zone;
-
-public class NetworkWithZonesUtils {
-	// if CRSs of the network and zones are different, zoneFinder should convert between CRSs
-	public static IdMap<Link, Zone> createLinkToZoneMap(Network network, ZoneFinder zoneFinder) {
-		return network.getLinks()
-				.values()
-				.stream()
-				.collect(IdCollectors.toIdMap(Link.class, Identifiable::getId, l -> zoneFinder.findZone(l.getToNode().getCoord())));
-	}
-
-	public static IdMap<Node, Zone> createNodeToZoneMap(Network network, ZoneFinder zoneFinder) {
-		return network.getNodes()
-				.values()
-				.stream()
-				.collect(IdCollectors.toIdMap(Node.class, Identifiable::getId, n -> zoneFinder.findZone(n.getCoord())));
-	}
-}
diff --git a/contribs/dvrp/src/test/java/org/matsim/contrib/zone/SquareGridTest.java b/contribs/dvrp/src/test/java/org/matsim/contrib/zone/SquareGridTest.java
deleted file mode 100644
index dc09184888e..00000000000
--- a/contribs/dvrp/src/test/java/org/matsim/contrib/zone/SquareGridTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * *********************************************************************** *
- * project: org.matsim.*
- * *********************************************************************** *
- *                                                                         *
- * copyright       : (C) 2020 by the members listed in the COPYING,        *
- *                   LICENSE and WARRANTY file.                            *
- * email           : info at matsim dot org                                *
- *                                                                         *
- * *********************************************************************** *
- *                                                                         *
- *   This program is free software; you can redistribute it and/or modify  *
- *   it under the terms of the GNU General Public License as published by  *
- *   the Free Software Foundation; either version 2 of the License, or     *
- *   (at your option) any later version.                                   *
- *   See also COPYING, LICENSE and WARRANTY file                           *
- *                                                                         *
- * *********************************************************************** *
- */
-
-package org.matsim.contrib.zone;
-
-import static org.assertj.core.api.Assertions.*;
-import static org.matsim.contrib.zone.SquareGrid.EPSILON;
-
-import java.util.List;
-
-import org.junit.jupiter.api.Test;
-import org.matsim.api.core.v01.Coord;
-import org.matsim.api.core.v01.Id;
-import org.matsim.api.core.v01.network.Node;
-import org.matsim.core.network.NetworkUtils;
-
-/**
- * @author Michal Maciejewski (michalm)
- */
-public class SquareGridTest {
-	@Test
-	void emptyNodes_fail() {
-		assertThatThrownBy(() -> new SquareGrid(List.of(), 100)).isExactlyInstanceOf(IllegalArgumentException.class)
-				.hasMessage("Cannot create SquareGrid if no nodes");
-	}
-
-	@Test
-	void outsideBoundaries_withinEpsilon_success() {
-		Node node_0_0 = node(0, 0);
-		SquareGrid grid = new SquareGrid(List.of(node_0_0), 100);
-		assertThatCode(() -> grid.getZone(new Coord(-EPSILON, EPSILON))).doesNotThrowAnyException();
-	}
-
-	@Test
-	void outsideBoundaries_outsideEpsilon_fail() {
-		Node node_0_0 = node(0, 0);
-		SquareGrid grid = new SquareGrid(List.of(node_0_0), 100);
-		assertThatThrownBy(() -> grid.getZone(new Coord(-2 * EPSILON, 0))).isExactlyInstanceOf(
-				IllegalArgumentException.class);
-	}
-
-	@Test
-	void testLazyZoneCreation() {
-		Node node_0_0 = node(0, 0);
-		SquareGrid grid = new SquareGrid(List.of(node_0_0), 100);
-
-		Coord coord = new Coord(0, 0);
-		Zone zone = new Zone(Id.create(0, Zone.class), "square", new Coord(49, 49));
-		assertThat(grid.getZone(coord)).isNull();
-		assertThat(grid.getOrCreateZone(coord)).isEqualToComparingFieldByField(zone);
-		assertThat(grid.getZone(coord)).isEqualToComparingFieldByField(zone);
-	}
-
-	@Test
-	void testGrid() {
-		Node node_0_0 = node(0, 0);
-		Node node_150_150 = node(150, 150);
-		SquareGrid grid = new SquareGrid(List.of(node_0_0, node_150_150), 100);
-
-		Coord coord0 = new Coord(100 - 2 * EPSILON, 100 - 2 * EPSILON);
-		Zone zone0 = new Zone(Id.create(0, Zone.class), "square", new Coord(49, 49));
-		assertThat(grid.getOrCreateZone(coord0)).isEqualToComparingFieldByField(zone0);
-
-		Coord coord1 = new Coord(100 - EPSILON, 100 - EPSILON);
-		Zone zone1 = new Zone(Id.create(3, Zone.class), "square", new Coord(149, 149));
-		assertThat(grid.getOrCreateZone(coord1)).isEqualToComparingFieldByField(zone1);
-	}
-
-	private Node node(double x, double y) {
-		return NetworkUtils.createNode(Id.createNodeId(x + "," + y), new Coord(x, y));
-	}
-}
diff --git a/contribs/dvrp/src/test/java/org/matsim/contrib/zone/skims/FreeSpeedTravelTimeMatrixTest.java b/contribs/dvrp/src/test/java/org/matsim/contrib/zone/skims/FreeSpeedTravelTimeMatrixTest.java
index c5743ffaafb..8aaf6001258 100644
--- a/contribs/dvrp/src/test/java/org/matsim/contrib/zone/skims/FreeSpeedTravelTimeMatrixTest.java
+++ b/contribs/dvrp/src/test/java/org/matsim/contrib/zone/skims/FreeSpeedTravelTimeMatrixTest.java
@@ -27,6 +27,8 @@
 import org.matsim.api.core.v01.Id;
 import org.matsim.api.core.v01.network.Network;
 import org.matsim.api.core.v01.network.Node;
+import org.matsim.contrib.common.zones.ZoneSystem;
+import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystem;
 import org.matsim.core.network.NetworkUtils;
 
 /**
@@ -49,9 +51,9 @@ public FreeSpeedTravelTimeMatrixTest() {
 	@Test
 	void matrix() {
 		DvrpTravelTimeMatrixParams params = new DvrpTravelTimeMatrixParams();
-		params.cellSize = 100;
 		params.maxNeighborDistance = 0;
-		var matrix = FreeSpeedTravelTimeMatrix.createFreeSpeedMatrix(network, params, 1, 1);
+		ZoneSystem zoneSystem = new SquareGridZoneSystem(network, 100.);
+		var matrix = FreeSpeedTravelTimeMatrix.createFreeSpeedMatrix(network, zoneSystem, params, 1, 1);
 
 		// distances between central nodes: A and B
 		assertThat(matrix.getTravelTime(nodeA, nodeA, 0)).isEqualTo(0);
@@ -69,9 +71,10 @@ void matrix() {
 	@Test
 	void sparseMatrix() {
 		DvrpTravelTimeMatrixParams params = new DvrpTravelTimeMatrixParams();
-		params.cellSize = 100;
 		params.maxNeighborDistance = 9999;
-		var matrix = FreeSpeedTravelTimeMatrix.createFreeSpeedMatrix(network, params, 1, 1);
+
+		ZoneSystem zoneSystem = new SquareGridZoneSystem(network, 100.);
+		var matrix = FreeSpeedTravelTimeMatrix.createFreeSpeedMatrix(network, zoneSystem, params, 1, 1);
 
 		// distances between central nodes: A and B
 		assertThat(matrix.getTravelTime(nodeA, nodeA, 0)).isEqualTo(0);
diff --git a/contribs/dvrp/src/test/java/org/matsim/contrib/zone/skims/MatrixTest.java b/contribs/dvrp/src/test/java/org/matsim/contrib/zone/skims/MatrixTest.java
index 02e6f9fc5dd..fab91039ecc 100644
--- a/contribs/dvrp/src/test/java/org/matsim/contrib/zone/skims/MatrixTest.java
+++ b/contribs/dvrp/src/test/java/org/matsim/contrib/zone/skims/MatrixTest.java
@@ -28,17 +28,18 @@
 
 import org.junit.jupiter.api.Test;
 import org.matsim.api.core.v01.Id;
-import org.matsim.contrib.zone.Zone;
+import org.matsim.contrib.common.zones.Zone;
+import org.matsim.contrib.common.zones.ZoneImpl;
 
 /**
  * @author Michal Maciejewski (michalm)
  */
 public class MatrixTest {
-	private final Zone unknownZone = new Zone(Id.create("?", Zone.class), null);
+	private final Zone unknownZone = new ZoneImpl(Id.create("?", Zone.class), null, null, null);
 
-	private final Zone zoneA = new Zone(Id.create("A", Zone.class), null);
-	private final Zone zoneB = new Zone(Id.create("B", Zone.class), null);
-	private final Zone zoneC = new Zone(Id.create("C", Zone.class), null);
+	private final Zone zoneA = new ZoneImpl(Id.create("A", Zone.class), null, null, null);
+	private final Zone zoneB = new ZoneImpl(Id.create("B", Zone.class), null, null, null);
+	private final Zone zoneC = new ZoneImpl(Id.create("C", Zone.class), null, null, null);
 
 	private final Set<Zone> zones = Set.of(zoneA, zoneB, zoneC);
 	private final Matrix matrix = new Matrix(zones);
diff --git a/contribs/dvrp/src/test/java/org/matsim/contrib/zone/skims/TravelTimeMatricesTest.java b/contribs/dvrp/src/test/java/org/matsim/contrib/zone/skims/TravelTimeMatricesTest.java
index 02d7b7bd241..39a17e954a7 100644
--- a/contribs/dvrp/src/test/java/org/matsim/contrib/zone/skims/TravelTimeMatricesTest.java
+++ b/contribs/dvrp/src/test/java/org/matsim/contrib/zone/skims/TravelTimeMatricesTest.java
@@ -29,8 +29,9 @@
 import org.matsim.api.core.v01.Id;
 import org.matsim.api.core.v01.network.Network;
 import org.matsim.api.core.v01.network.Node;
+import org.matsim.contrib.common.zones.Zone;
+import org.matsim.contrib.common.zones.ZoneImpl;
 import org.matsim.contrib.dvrp.router.TimeAsTravelDisutility;
-import org.matsim.contrib.zone.Zone;
 import org.matsim.core.network.NetworkUtils;
 import org.matsim.core.trafficmonitoring.FreeSpeedTravelTime;
 
@@ -45,8 +46,8 @@ void travelTimeMatrix() {
 		Node nodeB = NetworkUtils.createAndAddNode(network, Id.createNodeId("B"), new Coord(150, 150));
 		NetworkUtils.createAndAddLink(network, Id.createLinkId("AB"), nodeA, nodeB, 150, 15, 20, 1);
 		NetworkUtils.createAndAddLink(network, Id.createLinkId("BA"), nodeB, nodeA, 300, 15, 40, 1);
-		Zone zoneA = new Zone(Id.create("Zone_A", Zone.class), null);
-		Zone zoneB = new Zone(Id.create("Zone_Z", Zone.class), null);
+		Zone zoneA = new ZoneImpl(Id.create("Zone_A", Zone.class), null, null, null);
+		Zone zoneB = new ZoneImpl(Id.create("Zone_Z", Zone.class), null, null, null);
 
 		var centralNodes = Map.of(zoneA, nodeA, zoneB, nodeB);
 		var matrix = TravelTimeMatrices.calculateTravelTimeMatrix(routingParams(network), centralNodes, 0);
diff --git a/contribs/taxi/src/main/java/org/matsim/contrib/etaxi/optimizer/ETaxiModeOptimizerQSimModule.java b/contribs/taxi/src/main/java/org/matsim/contrib/etaxi/optimizer/ETaxiModeOptimizerQSimModule.java
index 3c42d7111c2..5860bea3890 100644
--- a/contribs/taxi/src/main/java/org/matsim/contrib/etaxi/optimizer/ETaxiModeOptimizerQSimModule.java
+++ b/contribs/taxi/src/main/java/org/matsim/contrib/etaxi/optimizer/ETaxiModeOptimizerQSimModule.java
@@ -86,7 +86,7 @@ public TaxiOptimizer get() {
 						var chargingInfrastructure = getModalInstance(ChargingInfrastructure.class);
 						var scheduleTimingUpdater = getModalInstance(ScheduleTimingUpdater.class);
 						return new ETaxiOptimizerProvider(events, taxiCfg, fleet, network, timer, travelTime,
-								travelDisutility, eTaxiScheduler, scheduleTimingUpdater, chargingInfrastructure).get();
+								travelDisutility, eTaxiScheduler, scheduleTimingUpdater, chargingInfrastructure, getConfig()).get();
 					}
 				});
 
diff --git a/contribs/taxi/src/main/java/org/matsim/contrib/etaxi/optimizer/ETaxiOptimizerProvider.java b/contribs/taxi/src/main/java/org/matsim/contrib/etaxi/optimizer/ETaxiOptimizerProvider.java
index e3b653a0b86..30458550f44 100644
--- a/contribs/taxi/src/main/java/org/matsim/contrib/etaxi/optimizer/ETaxiOptimizerProvider.java
+++ b/contribs/taxi/src/main/java/org/matsim/contrib/etaxi/optimizer/ETaxiOptimizerProvider.java
@@ -19,7 +19,10 @@
 
 package org.matsim.contrib.etaxi.optimizer;
 
+import com.google.inject.Provider;
 import org.matsim.api.core.v01.network.Network;
+import org.matsim.contrib.common.zones.ZoneSystem;
+import org.matsim.contrib.common.zones.ZoneSystemUtils;
 import org.matsim.contrib.dvrp.fleet.Fleet;
 import org.matsim.contrib.dvrp.schedule.ScheduleTimingUpdater;
 import org.matsim.contrib.etaxi.ETaxiScheduler;
@@ -30,23 +33,16 @@
 import org.matsim.contrib.ev.infrastructure.ChargingInfrastructure;
 import org.matsim.contrib.taxi.optimizer.BestDispatchFinder;
 import org.matsim.contrib.taxi.optimizer.TaxiOptimizer;
-import org.matsim.contrib.taxi.optimizer.rules.IdleTaxiZonalRegistry;
-import org.matsim.contrib.taxi.optimizer.rules.RuleBasedRequestInserter;
-import org.matsim.contrib.taxi.optimizer.rules.RuleBasedTaxiOptimizerParams;
-import org.matsim.contrib.taxi.optimizer.rules.UnplannedRequestZonalRegistry;
-import org.matsim.contrib.taxi.optimizer.rules.ZonalRegisters;
+import org.matsim.contrib.taxi.optimizer.rules.*;
 import org.matsim.contrib.taxi.run.TaxiConfigGroup;
-import org.matsim.contrib.zone.SquareGridSystem;
-import org.matsim.contrib.zone.ZonalSystem;
 import org.matsim.core.api.experimental.events.EventsManager;
+import org.matsim.core.config.Config;
 import org.matsim.core.mobsim.framework.MobsimTimer;
 import org.matsim.core.router.speedy.SpeedyALTFactory;
 import org.matsim.core.router.util.LeastCostPathCalculator;
 import org.matsim.core.router.util.TravelDisutility;
 import org.matsim.core.router.util.TravelTime;
 
-import com.google.inject.Provider;
-
 public class ETaxiOptimizerProvider implements Provider<TaxiOptimizer> {
 	private final EventsManager eventsManager;
 	private final TaxiConfigGroup taxiCfg;
@@ -57,11 +53,12 @@ public class ETaxiOptimizerProvider implements Provider<TaxiOptimizer> {
 	private final TravelDisutility travelDisutility;
 	private final ETaxiScheduler eScheduler;
 	private final ChargingInfrastructure chargingInfrastructure;
+	private final Config config;
 	private final ScheduleTimingUpdater scheduleTimingUpdater;
 
 	public ETaxiOptimizerProvider(EventsManager eventsManager, TaxiConfigGroup taxiCfg, Fleet fleet, Network network,
 			MobsimTimer timer, TravelTime travelTime, TravelDisutility travelDisutility, ETaxiScheduler eScheduler,
-			ScheduleTimingUpdater scheduleTimingUpdater, ChargingInfrastructure chargingInfrastructure) {
+			ScheduleTimingUpdater scheduleTimingUpdater, ChargingInfrastructure chargingInfrastructure, Config config) {
 		this.eventsManager = eventsManager;
 		this.taxiCfg = taxiCfg;
 		this.fleet = fleet;
@@ -72,6 +69,7 @@ public ETaxiOptimizerProvider(EventsManager eventsManager, TaxiConfigGroup taxiC
 		this.eScheduler = eScheduler;
 		this.scheduleTimingUpdater = scheduleTimingUpdater;
 		this.chargingInfrastructure = chargingInfrastructure;
+		this.config = config;
 	}
 
 	@Override
@@ -79,7 +77,7 @@ public TaxiOptimizer get() {
 		String type = taxiCfg.getTaxiOptimizerParams().getName();
 		if (type.equals(RuleBasedETaxiOptimizerParams.SET_NAME)) {
 			ZonalRegisters zonalRegisters = createZonalRegisters(
-					((RuleBasedETaxiOptimizerParams)taxiCfg.getTaxiOptimizerParams()).getRuleBasedTaxiOptimizerParams());
+					((RuleBasedETaxiOptimizerParams)taxiCfg.getTaxiOptimizerParams()).getRuleBasedTaxiOptimizerParams(), config);
 			BestDispatchFinder dispatchFinder = new BestDispatchFinder(eScheduler.getScheduleInquiry(), network, timer,
 					travelTime, travelDisutility);
 			RuleBasedRequestInserter requestInserter = new RuleBasedRequestInserter(eScheduler, timer, dispatchFinder,
@@ -99,11 +97,12 @@ public TaxiOptimizer get() {
 		}
 	}
 
-	private ZonalRegisters createZonalRegisters(RuleBasedTaxiOptimizerParams params) {
-		ZonalSystem zonalSystem = new SquareGridSystem(network.getNodes().values(), params.cellSize);
-		IdleTaxiZonalRegistry idleTaxiRegistry = new IdleTaxiZonalRegistry(zonalSystem,
+	private ZonalRegisters createZonalRegisters(RuleBasedTaxiOptimizerParams params, Config config) {
+		ZoneSystem zoneSystem = ZoneSystemUtils.createZoneSystem(config.getContext(), network,
+			params.getZoneSystemParams(), config.global().getCoordinateSystem());
+		IdleTaxiZonalRegistry idleTaxiRegistry = new IdleTaxiZonalRegistry(zoneSystem,
 				eScheduler.getScheduleInquiry());
-		UnplannedRequestZonalRegistry unplannedRequestRegistry = new UnplannedRequestZonalRegistry(zonalSystem);
+		UnplannedRequestZonalRegistry unplannedRequestRegistry = new UnplannedRequestZonalRegistry(zoneSystem);
 		return new ZonalRegisters(idleTaxiRegistry, unplannedRequestRegistry);
 	}
 }
diff --git a/contribs/taxi/src/main/java/org/matsim/contrib/etaxi/optimizer/assignment/AssignmentETaxiOptimizerParams.java b/contribs/taxi/src/main/java/org/matsim/contrib/etaxi/optimizer/assignment/AssignmentETaxiOptimizerParams.java
index 3ca8be96021..eaa42b990e6 100644
--- a/contribs/taxi/src/main/java/org/matsim/contrib/etaxi/optimizer/assignment/AssignmentETaxiOptimizerParams.java
+++ b/contribs/taxi/src/main/java/org/matsim/contrib/etaxi/optimizer/assignment/AssignmentETaxiOptimizerParams.java
@@ -21,6 +21,7 @@
 
 import org.matsim.contrib.taxi.optimizer.AbstractTaxiOptimizerParams;
 import org.matsim.contrib.taxi.optimizer.assignment.AssignmentTaxiOptimizerParams;
+import org.matsim.contrib.taxi.optimizer.rules.RuleBasedTaxiOptimizerParams;
 import org.matsim.core.config.ConfigGroup;
 
 import jakarta.validation.constraints.DecimalMax;
@@ -52,21 +53,14 @@ public final class AssignmentETaxiOptimizerParams extends AbstractTaxiOptimizerP
 
 	public AssignmentETaxiOptimizerParams() {
 		super(SET_NAME, true, true);
+		initSingletonParameterSets();
 	}
 
-	@Override
-	public ConfigGroup createParameterSet(String type) {
-		return type.equals(AssignmentTaxiOptimizerParams.SET_NAME) ?
-				new AssignmentTaxiOptimizerParams() :
-				super.createParameterSet(type);
-	}
-
-	@Override
-	public void addParameterSet(ConfigGroup set) {
-		if (set.getName().equals(AssignmentTaxiOptimizerParams.SET_NAME)) {
-			assignmentTaxiOptimizerParams = (AssignmentTaxiOptimizerParams)set;
-		}
-		super.addParameterSet(set);
+	private void initSingletonParameterSets() {
+		//insertion search params (one of: extensive, selective, repeated selective)
+		addDefinition(AssignmentTaxiOptimizerParams.SET_NAME, AssignmentTaxiOptimizerParams::new,
+			() -> assignmentTaxiOptimizerParams,
+			params -> assignmentTaxiOptimizerParams = (AssignmentTaxiOptimizerParams)params);
 	}
 
 	AssignmentTaxiOptimizerParams getAssignmentTaxiOptimizerParams() {
diff --git a/contribs/taxi/src/main/java/org/matsim/contrib/etaxi/optimizer/rules/RuleBasedETaxiOptimizerParams.java b/contribs/taxi/src/main/java/org/matsim/contrib/etaxi/optimizer/rules/RuleBasedETaxiOptimizerParams.java
index b1525ddc8f6..f8543567a2e 100644
--- a/contribs/taxi/src/main/java/org/matsim/contrib/etaxi/optimizer/rules/RuleBasedETaxiOptimizerParams.java
+++ b/contribs/taxi/src/main/java/org/matsim/contrib/etaxi/optimizer/rules/RuleBasedETaxiOptimizerParams.java
@@ -19,6 +19,9 @@
 
 package org.matsim.contrib.etaxi.optimizer.rules;
 
+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.square.SquareGridZoneSystemParams;
 import org.matsim.contrib.taxi.optimizer.AbstractTaxiOptimizerParams;
 import org.matsim.contrib.taxi.optimizer.rules.RuleBasedTaxiOptimizerParams;
 import org.matsim.core.config.ConfigGroup;
@@ -52,21 +55,14 @@ public final class RuleBasedETaxiOptimizerParams extends AbstractTaxiOptimizerPa
 
 	public RuleBasedETaxiOptimizerParams() {
 		super(SET_NAME, false, false);
+		initSingletonParameterSets();
 	}
 
-	@Override
-	public ConfigGroup createParameterSet(String type) {
-		return type.equals(RuleBasedTaxiOptimizerParams.SET_NAME) ?
-				new RuleBasedTaxiOptimizerParams() :
-				super.createParameterSet(type);
-	}
-
-	@Override
-	public void addParameterSet(ConfigGroup set) {
-		if (set.getName().equals(RuleBasedTaxiOptimizerParams.SET_NAME)) {
-			ruleBasedTaxiOptimizerParams = (RuleBasedTaxiOptimizerParams)set;
-		}
-		super.addParameterSet(set);
+	private void initSingletonParameterSets() {
+		//insertion search params (one of: extensive, selective, repeated selective)
+		addDefinition(RuleBasedTaxiOptimizerParams.SET_NAME, RuleBasedTaxiOptimizerParams::new,
+			() -> ruleBasedTaxiOptimizerParams,
+			params -> ruleBasedTaxiOptimizerParams = (RuleBasedTaxiOptimizerParams)params);
 	}
 
 	public RuleBasedTaxiOptimizerParams getRuleBasedTaxiOptimizerParams() {
diff --git a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/AbstractTaxiOptimizerParams.java b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/AbstractTaxiOptimizerParams.java
index 32f6272ce9a..176fdbce2a8 100644
--- a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/AbstractTaxiOptimizerParams.java
+++ b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/AbstractTaxiOptimizerParams.java
@@ -23,12 +23,13 @@
 
 import jakarta.validation.constraints.Positive;
 
+import org.matsim.contrib.common.util.ReflectiveConfigGroupWithConfigurableParameterSets;
 import org.matsim.core.config.ReflectiveConfigGroup;
 
 /**
  * @author michalm
  */
-public abstract class AbstractTaxiOptimizerParams extends ReflectiveConfigGroup {
+public abstract class AbstractTaxiOptimizerParams extends ReflectiveConfigGroupWithConfigurableParameterSets {
 	public static final String REOPTIMIZATION_TIME_STEP = "reoptimizationTimeStep";
 	protected static final String REOPTIMIZATION_TIME_STEP_EXP = "Specifies how often the reoptimization algorithm is executed."
 			+ " Must be a positive integer value. Smaller values mean lower reaction time."
diff --git a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/DefaultTaxiOptimizerProvider.java b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/DefaultTaxiOptimizerProvider.java
index ed28f925ae7..a9e706f10ea 100644
--- a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/DefaultTaxiOptimizerProvider.java
+++ b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/DefaultTaxiOptimizerProvider.java
@@ -22,6 +22,8 @@
 import java.net.URL;
 
 import org.matsim.api.core.v01.network.Network;
+import org.matsim.contrib.common.zones.ZoneSystem;
+import org.matsim.contrib.common.zones.ZoneSystemUtils;
 import org.matsim.contrib.dvrp.fleet.Fleet;
 import org.matsim.contrib.dvrp.schedule.ScheduleTimingUpdater;
 import org.matsim.contrib.taxi.optimizer.assignment.AssignmentRequestInserter;
@@ -38,8 +40,7 @@
 import org.matsim.contrib.taxi.optimizer.zonal.ZonalTaxiOptimizerParams;
 import org.matsim.contrib.taxi.run.TaxiConfigGroup;
 import org.matsim.contrib.taxi.scheduler.TaxiScheduler;
-import org.matsim.contrib.zone.SquareGridSystem;
-import org.matsim.contrib.zone.ZonalSystem;
+import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystem;
 import org.matsim.core.api.experimental.events.EventsManager;
 import org.matsim.core.mobsim.framework.MobsimTimer;
 import org.matsim.core.router.util.TravelDisutility;
@@ -116,9 +117,9 @@ yield new RuleBasedTaxiOptimizer(eventsManager, taxiCfg, fleet, scheduler, sched
 	}
 
 	private ZonalRegisters createZonalRegisters(RuleBasedTaxiOptimizerParams params) {
-		ZonalSystem zonalSystem = new SquareGridSystem(network.getNodes().values(), params.cellSize);
-		IdleTaxiZonalRegistry idleTaxiRegistry = new IdleTaxiZonalRegistry(zonalSystem, scheduler.getScheduleInquiry());
-		UnplannedRequestZonalRegistry unplannedRequestRegistry = new UnplannedRequestZonalRegistry(zonalSystem);
+		ZoneSystem zoneSystem = ZoneSystemUtils.createZoneSystem(context, network, params.getZoneSystemParams());
+		IdleTaxiZonalRegistry idleTaxiRegistry = new IdleTaxiZonalRegistry(zoneSystem, scheduler.getScheduleInquiry());
+		UnplannedRequestZonalRegistry unplannedRequestRegistry = new UnplannedRequestZonalRegistry(zoneSystem);
 		return new ZonalRegisters(idleTaxiRegistry, unplannedRequestRegistry);
 	}
 }
diff --git a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/rules/IdleTaxiZonalRegistry.java b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/rules/IdleTaxiZonalRegistry.java
index 670c2f8e283..37243ddb127 100644
--- a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/rules/IdleTaxiZonalRegistry.java
+++ b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/rules/IdleTaxiZonalRegistry.java
@@ -28,30 +28,30 @@
 import org.matsim.api.core.v01.Id;
 import org.matsim.api.core.v01.IdMap;
 import org.matsim.api.core.v01.network.Node;
+import org.matsim.contrib.common.zones.Zone;
+import org.matsim.contrib.common.zones.ZoneSystem;
+import org.matsim.contrib.common.zones.ZoneSystemUtils;
 import org.matsim.contrib.dvrp.fleet.DvrpVehicle;
 import org.matsim.contrib.dvrp.schedule.ScheduleInquiry;
 import org.matsim.contrib.dvrp.schedule.Schedules;
 import org.matsim.contrib.taxi.schedule.TaxiStayTask;
-import org.matsim.contrib.zone.ZonalSystem;
-import org.matsim.contrib.zone.ZonalSystems;
-import org.matsim.contrib.zone.Zone;
 
 public class IdleTaxiZonalRegistry {
 	private final ScheduleInquiry scheduleInquiry;
 
-	private final ZonalSystem zonalSystem;
+	private final ZoneSystem zoneSystem;
 	private final IdMap<Zone, List<Zone>> zonesSortedByDistance;
 
 	private final IdMap<Zone, Map<Id<DvrpVehicle>, DvrpVehicle>> vehiclesInZones = new IdMap<>(Zone.class);
 	private final Map<Id<DvrpVehicle>, DvrpVehicle> vehicles = new LinkedHashMap<>();
 
-	public IdleTaxiZonalRegistry(ZonalSystem zonalSystem, ScheduleInquiry scheduleInquiry) {
+	public IdleTaxiZonalRegistry(ZoneSystem zoneSystem, ScheduleInquiry scheduleInquiry) {
 		this.scheduleInquiry = scheduleInquiry;
 
-		this.zonalSystem = zonalSystem;
-		zonesSortedByDistance = ZonalSystems.initZonesByDistance(zonalSystem.getZones());
+		this.zoneSystem = zoneSystem;
+		zonesSortedByDistance = ZoneSystemUtils.initZonesByDistance(zoneSystem.getZones());
 
-		for (Id<Zone> id : zonalSystem.getZones().keySet()) {
+		for (Id<Zone> id : zoneSystem.getZones().keySet()) {
 			vehiclesInZones.put(id, new LinkedHashMap<>());//LinkedHashMap to preserve iteration order
 		}
 	}
@@ -91,7 +91,7 @@ public Stream<DvrpVehicle> findNearestVehicles(Node node, int minCount, Predicat
 
 		return minCount >= vehicles.size() ?
 				vehicles.values().stream().filter(idleVehicleFilter) :
-				zonesSortedByDistance.get(zonalSystem.getZone(node).getId())
+				zonesSortedByDistance.get(zoneSystem.getZoneForNodeId(node.getId()).orElseThrow().getId())
 						.stream()
 						.flatMap(z -> vehiclesInZones.get(z.getId()).values().stream())
 						.filter(idleVehicleFilter)
@@ -99,7 +99,7 @@ public Stream<DvrpVehicle> findNearestVehicles(Node node, int minCount, Predicat
 	}
 
 	private Id<Zone> getZoneId(TaxiStayTask stayTask) {
-		return zonalSystem.getZone(stayTask.getLink().getToNode()).getId();
+		return zoneSystem.getZoneForLinkId(stayTask.getLink().getId()).orElseThrow().getId();
 	}
 
 	public Stream<DvrpVehicle> vehicles() {
diff --git a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerParams.java b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerParams.java
index 627fcacb253..c02e1e5a5e8 100644
--- a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerParams.java
+++ b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerParams.java
@@ -19,6 +19,10 @@
 
 package org.matsim.contrib.taxi.optimizer.rules;
 
+import org.matsim.contrib.common.zones.ZoneSystemParams;
+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.square.SquareGridZoneSystemParams;
 import org.matsim.contrib.taxi.optimizer.AbstractTaxiOptimizerParams;
 import org.matsim.contrib.taxi.optimizer.rules.RuleBasedRequestInserter.Goal;
 
@@ -62,14 +66,23 @@ public final class RuleBasedTaxiOptimizerParams extends AbstractTaxiOptimizerPar
 	@Positive
 	public int nearestVehiclesLimit = 30;
 
-	@Parameter
-	@Comment("The side length of square zones used in zonal registers of idle vehicles"
-			+ " and open requests. The default value is 1000 m. This value is good for urban areas. For large areas"
-			+ " with sparsely distributed taxis and low taxi demand, you may consider using a bigger cell size."
-			+ " On the other hand, if 'nearestRequestsLimit' or 'nearestVehiclesLimit' are very low,"
-			+ " a smaller cell size may work better.")
-	@Positive
-	public double cellSize = 1000;
+	private ZoneSystemParams zoneSystemParams = new SquareGridZoneSystemParams();
+
+	private void initSingletonParameterSets() {
+
+		//insertion search params (one of: extensive, selective, repeated selective)
+		addDefinition(SquareGridZoneSystemParams.SET_NAME, SquareGridZoneSystemParams::new,
+			() -> zoneSystemParams,
+			params -> zoneSystemParams = (SquareGridZoneSystemParams)params);
+
+		addDefinition(GISFileZoneSystemParams.SET_NAME, GISFileZoneSystemParams::new,
+			() -> zoneSystemParams,
+			params -> zoneSystemParams = (GISFileZoneSystemParams)params);
+
+		addDefinition(H3GridZoneSystemParams.SET_NAME, H3GridZoneSystemParams::new,
+			() -> zoneSystemParams,
+			params -> zoneSystemParams = (H3GridZoneSystemParams)params);
+	}
 
 	/**
 	 * {@value #REOPTIMIZATION_TIME_STEP_EXP}
@@ -81,9 +94,19 @@ public final class RuleBasedTaxiOptimizerParams extends AbstractTaxiOptimizerPar
 
 	public RuleBasedTaxiOptimizerParams() {
 		super(SET_NAME, false, false);
+		initSingletonParameterSets();
+		initDefault();
+	}
+
+	private void initDefault() {
+		((SquareGridZoneSystemParams) zoneSystemParams).cellSize = 1000.;
 	}
 
 	public int getReoptimizationTimeStep() {
 		return reoptimizationTimeStep;
 	}
+
+	public ZoneSystemParams getZoneSystemParams() {
+		return zoneSystemParams;
+	}
 }
diff --git a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/rules/UnplannedRequestZonalRegistry.java b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/rules/UnplannedRequestZonalRegistry.java
index a2c061d7f68..233aee0db90 100644
--- a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/rules/UnplannedRequestZonalRegistry.java
+++ b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/rules/UnplannedRequestZonalRegistry.java
@@ -27,24 +27,24 @@
 import org.matsim.api.core.v01.Id;
 import org.matsim.api.core.v01.IdMap;
 import org.matsim.api.core.v01.network.Node;
+import org.matsim.contrib.common.zones.Zone;
+import org.matsim.contrib.common.zones.ZoneSystem;
+import org.matsim.contrib.common.zones.ZoneSystemUtils;
 import org.matsim.contrib.drt.passenger.DrtRequest;
 import org.matsim.contrib.dvrp.optimizer.Request;
-import org.matsim.contrib.zone.ZonalSystem;
-import org.matsim.contrib.zone.ZonalSystems;
-import org.matsim.contrib.zone.Zone;
 
 public class UnplannedRequestZonalRegistry {
-	private final ZonalSystem zonalSystem;
+	private final ZoneSystem zoneSystem;
 	private final IdMap<Zone, List<Zone>> zonesSortedByDistance;
 	private final IdMap<Zone, Map<Id<Request>, DrtRequest>> requestsInZones = new IdMap<>(Zone.class);
 
 	private int requestCount = 0;
 
-	public UnplannedRequestZonalRegistry(ZonalSystem zonalSystem) {
-		this.zonalSystem = zonalSystem;
-		zonesSortedByDistance = ZonalSystems.initZonesByDistance(zonalSystem.getZones());
+	public UnplannedRequestZonalRegistry(ZoneSystem zoneSystem) {
+		this.zoneSystem = zoneSystem;
+		zonesSortedByDistance = ZoneSystemUtils.initZonesByDistance(zoneSystem.getZones());
 
-		for (Id<Zone> id : zonalSystem.getZones().keySet()) {
+		for (Id<Zone> id : zoneSystem.getZones().keySet()) {
 			requestsInZones.put(id, new LinkedHashMap<>());//LinkedHashMap to preserve iteration order
 		}
 	}
@@ -72,14 +72,14 @@ public void removeRequest(DrtRequest request) {
 	}
 
 	public Stream<DrtRequest> findNearestRequests(Node node, int minCount) {
-		return zonesSortedByDistance.get(zonalSystem.getZone(node).getId())
+		return zonesSortedByDistance.get(zoneSystem.getZoneForNodeId(node.getId()).orElseThrow().getId())
 				.stream()
 				.flatMap(z -> requestsInZones.get(z.getId()).values().stream())
 				.limit(minCount);
 	}
 
 	private Id<Zone> getZoneId(DrtRequest request) {
-		return zonalSystem.getZone(request.getFromLink().getFromNode()).getId();
+		return zoneSystem.getZoneForNodeId(request.getFromLink().getFromNode().getId()).orElseThrow().getId();
 	}
 
 	public int getRequestCount() {
diff --git a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/zonal/ZonalRequestInserter.java b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/zonal/ZonalRequestInserter.java
index ad971ec8da0..cdba2fc9b00 100644
--- a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/zonal/ZonalRequestInserter.java
+++ b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/zonal/ZonalRequestInserter.java
@@ -30,6 +30,8 @@
 import org.matsim.api.core.v01.IdMap;
 import org.matsim.api.core.v01.network.Link;
 import org.matsim.api.core.v01.network.Network;
+import org.matsim.contrib.common.zones.Zone;
+import org.matsim.contrib.common.zones.ZoneSystemUtils;
 import org.matsim.contrib.drt.passenger.DrtRequest;
 import org.matsim.contrib.dvrp.fleet.DvrpVehicle;
 import org.matsim.contrib.dvrp.fleet.Fleet;
@@ -40,10 +42,7 @@
 import org.matsim.contrib.taxi.optimizer.rules.ZonalRegisters;
 import org.matsim.contrib.taxi.scheduler.TaxiScheduler;
 import org.matsim.contrib.zone.ZonalSystemParams;
-import org.matsim.contrib.zone.Zone;
-import org.matsim.contrib.zone.Zones;
-import org.matsim.contrib.zone.util.NetworkWithZonesUtils;
-import org.matsim.contrib.zone.util.ZoneFinderImpl;
+import org.matsim.contrib.common.zones.util.ZoneFinderImpl;
 import org.matsim.core.config.ConfigGroup;
 import org.matsim.core.mobsim.framework.MobsimTimer;
 import org.matsim.core.router.util.TravelDisutility;
@@ -74,11 +73,11 @@ public ZonalRequestInserter(Fleet fleet, TaxiScheduler scheduler, MobsimTimer ti
 				zonalRegisters);
 
 		ZonalSystemParams zonalSystemParams = params.getZonalSystemParams();
-		zones = Zones.readZones(ConfigGroup.getInputFileURL(context, zonalSystemParams.zonesXmlFile),
+		zones = ZoneSystemUtils.readZones(ConfigGroup.getInputFileURL(context, zonalSystemParams.zonesXmlFile),
 				ConfigGroup.getInputFileURL(context, zonalSystemParams.zonesShpFile));
 		// TODO No conversion of SRS is done
 
-		this.linkToZone = NetworkWithZonesUtils.createLinkToZoneMap(network, new ZoneFinderImpl(zones, zonalSystemParams.expansionDistance));
+		this.linkToZone = ZoneSystemUtils.createLinkToZoneMap(network, new ZoneFinderImpl(zones, zonalSystemParams.expansionDistance));
 
 		// FIXME zonal system used in RuleBasedTaxiOptim (for registers) should be equivalent to
 		// the zones used in ZonalTaxiOptim (for dispatching)
diff --git a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/zonal/ZonalTaxiOptimizerParams.java b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/zonal/ZonalTaxiOptimizerParams.java
index 60fa239ef4e..7076e48acbf 100644
--- a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/zonal/ZonalTaxiOptimizerParams.java
+++ b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/zonal/ZonalTaxiOptimizerParams.java
@@ -20,6 +20,7 @@
 package org.matsim.contrib.taxi.optimizer.zonal;
 
 import org.matsim.contrib.taxi.optimizer.AbstractTaxiOptimizerParams;
+import org.matsim.contrib.taxi.optimizer.assignment.AssignmentTaxiOptimizerParams;
 import org.matsim.contrib.taxi.optimizer.rules.RuleBasedTaxiOptimizerParams;
 import org.matsim.contrib.zone.ZonalSystemParams;
 import org.matsim.core.config.ConfigGroup;
@@ -32,26 +33,17 @@ public final class ZonalTaxiOptimizerParams extends AbstractTaxiOptimizerParams
 
 	public ZonalTaxiOptimizerParams() {
 		super(SET_NAME, false, false);
+		initSingletonParameterSets();
 	}
 
-	@Override
-	public ConfigGroup createParameterSet(String type) {
-		return switch (type) {
-			case RuleBasedTaxiOptimizerParams.SET_NAME -> new RuleBasedTaxiOptimizerParams();
-			case ZonalSystemParams.SET_NAME -> new ZonalSystemParams();
-			default -> super.createParameterSet(type);
-		};
-	}
-
-	@Override
-	public void addParameterSet(ConfigGroup set) {
-		switch (set.getName()) {
-			case RuleBasedTaxiOptimizerParams.SET_NAME ->
-					ruleBasedTaxiOptimizerParams = (RuleBasedTaxiOptimizerParams)set;
-			case ZonalSystemParams.SET_NAME -> zonalSystemParams = (ZonalSystemParams)set;
-		}
-
-		super.addParameterSet(set);
+	private void initSingletonParameterSets() {
+		//insertion search params (one of: extensive, selective, repeated selective)
+		addDefinition(RuleBasedTaxiOptimizerParams.SET_NAME, RuleBasedTaxiOptimizerParams::new,
+			() -> ruleBasedTaxiOptimizerParams,
+			params -> ruleBasedTaxiOptimizerParams = (RuleBasedTaxiOptimizerParams)params);
+		addDefinition(ZonalSystemParams.SET_NAME, ZonalSystemParams::new,
+			() -> zonalSystemParams,
+			params -> zonalSystemParams = (ZonalSystemParams)params);
 	}
 
 	public ZonalSystemParams getZonalSystemParams() {
diff --git a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/run/TaxiConfigGroup.java b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/run/TaxiConfigGroup.java
index daaaf36f914..5e76b1a8f41 100644
--- a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/run/TaxiConfigGroup.java
+++ b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/run/TaxiConfigGroup.java
@@ -36,7 +36,7 @@
 import org.matsim.contrib.taxi.optimizer.fifo.FifoTaxiOptimizerParams;
 import org.matsim.contrib.taxi.optimizer.rules.RuleBasedTaxiOptimizerParams;
 import org.matsim.contrib.taxi.optimizer.zonal.ZonalTaxiOptimizerParams;
-import org.matsim.contrib.util.ReflectiveConfigGroupWithConfigurableParameterSets;
+import org.matsim.contrib.common.util.ReflectiveConfigGroupWithConfigurableParameterSets;
 import org.matsim.core.config.Config;
 
 import com.google.common.base.Preconditions;
diff --git a/contribs/taxi/src/test/java/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT.java b/contribs/taxi/src/test/java/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT.java
index 5e029c8a29e..7138bf21c82 100644
--- a/contribs/taxi/src/test/java/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT.java
+++ b/contribs/taxi/src/test/java/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT.java
@@ -26,6 +26,7 @@
 import org.matsim.core.config.ConfigUtils;
 import org.matsim.core.events.EventsUtils;
 import org.matsim.core.population.PopulationUtils;
+import org.matsim.core.population.routes.PopulationComparison;
 import org.matsim.core.utils.io.IOUtils;
 import org.matsim.examples.ExamplesUtils;
 import org.matsim.testcases.MatsimTestUtils;
@@ -67,8 +68,9 @@ private void runScenario(String configPath) {
 			Population actual = PopulationUtils.createPopulation(ConfigUtils.createConfig());
 			PopulationUtils.readPopulation(actual, utils.getOutputDirectory() + "/output_plans.xml.gz");
 
-			boolean result = PopulationUtils.comparePopulations(expected, actual);
-			Assertions.assertTrue(result);
+			PopulationComparison populationComparison = new PopulationComparison();
+			PopulationComparison.Result result = populationComparison.compare(expected, actual);
+			Assertions.assertEquals(PopulationComparison.Result.equal, result);
 		}
 		{
 			String expected = utils.getInputDirectory() + "/output_events.xml.gz";
diff --git a/contribs/taxi/src/test/java/org/matsim/contrib/taxi/optimizer/TaxiOptimizerTests.java b/contribs/taxi/src/test/java/org/matsim/contrib/taxi/optimizer/TaxiOptimizerTests.java
index 3a3800467af..28dd7087e40 100644
--- a/contribs/taxi/src/test/java/org/matsim/contrib/taxi/optimizer/TaxiOptimizerTests.java
+++ b/contribs/taxi/src/test/java/org/matsim/contrib/taxi/optimizer/TaxiOptimizerTests.java
@@ -25,13 +25,16 @@
 import org.junit.jupiter.api.Assertions;
 import org.matsim.api.core.v01.Id;
 import org.matsim.api.core.v01.population.Population;
+import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystemParams;
 import org.matsim.contrib.dvrp.run.DvrpConfigGroup;
 import org.matsim.contrib.taxi.benchmark.RunTaxiBenchmark;
 import org.matsim.contrib.taxi.run.MultiModeTaxiConfigGroup;
 import org.matsim.contrib.taxi.run.TaxiConfigGroup;
+import org.matsim.contrib.zone.skims.DvrpTravelTimeMatrixParams;
 import org.matsim.core.config.ConfigUtils;
 import org.matsim.core.events.EventsUtils;
 import org.matsim.core.population.PopulationUtils;
+import org.matsim.core.population.routes.PopulationComparison;
 import org.matsim.core.utils.io.IOUtils;
 import org.matsim.examples.ExamplesUtils;
 import org.matsim.testcases.MatsimTestUtils;
@@ -40,9 +43,15 @@
 public class TaxiOptimizerTests {
 	public static void runBenchmark(boolean vehicleDiversion, AbstractTaxiOptimizerParams taxiOptimizerParams, MatsimTestUtils utils) {
 		Id.resetCaches();
+
+		DvrpConfigGroup dvrpConfig = new DvrpConfigGroup();
+		DvrpTravelTimeMatrixParams matrixParams = dvrpConfig.getTravelTimeMatrixParams();
+		matrixParams.addParameterSet(matrixParams.createParameterSet(SquareGridZoneSystemParams.SET_NAME));
+
+
 		// mielec taxi mini benchmark contains only the morning peak (6:00 - 12:00) that is shifted by -6 hours (i.e. 0:00 - 6:00).
 		URL configUrl = IOUtils.extendUrl(ExamplesUtils.getTestScenarioURL("mielec"), "mielec_taxi_mini_benchmark_config.xml");
-		var config = ConfigUtils.loadConfig(configUrl, new MultiModeTaxiConfigGroup(), new DvrpConfigGroup());
+		var config = ConfigUtils.loadConfig(configUrl, new MultiModeTaxiConfigGroup(), dvrpConfig);
 		config.controller().setOutputDirectory(utils.getOutputDirectory());
 
 		TaxiConfigGroup taxiCfg = TaxiConfigGroup.getSingleModeTaxiConfig(config);
@@ -64,8 +73,9 @@ public static void runBenchmark(boolean vehicleDiversion, AbstractTaxiOptimizerP
 			Population actual = PopulationUtils.createPopulation(ConfigUtils.createConfig());
 			PopulationUtils.readPopulation(actual, utils.getOutputDirectory() + "/output_plans.xml.gz");
 
-			boolean result = PopulationUtils.comparePopulations(expected, actual);
-			Assertions.assertTrue(result);
+			PopulationComparison populationComparison = new PopulationComparison();
+			PopulationComparison.Result result = populationComparison.compare(expected, actual);
+			Assertions.assertEquals(PopulationComparison.Result.equal, result);
 		}
 		{
 			String expected = utils.getInputDirectory() + "/output_events.xml.gz";
diff --git a/contribs/taxi/src/test/java/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT.java b/contribs/taxi/src/test/java/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT.java
index f70fc0d0d97..8189364d3ee 100644
--- a/contribs/taxi/src/test/java/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT.java
+++ b/contribs/taxi/src/test/java/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT.java
@@ -23,7 +23,9 @@
 
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
+import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystemParams;
 import org.matsim.contrib.taxi.optimizer.rules.RuleBasedRequestInserter.Goal;
+import org.matsim.core.config.ConfigGroup;
 import org.matsim.testcases.MatsimTestUtils;
 
 public class RuleBasedTaxiOptimizerIT {
@@ -36,7 +38,8 @@ void testRuleBased_dse() {
 		params.goal = Goal.DEMAND_SUPPLY_EQUIL;
 		params.nearestRequestsLimit = 99999;
 		params.nearestVehiclesLimit = 99999;
-		params.cellSize = 99999.;
+		SquareGridZoneSystemParams zoneParams = (SquareGridZoneSystemParams) params.getZoneSystemParams();
+		zoneParams.cellSize = 99999.;
 		runBenchmark(false, params, utils);
 	}
 
@@ -46,7 +49,8 @@ void testRuleBased_minWaitTime() {
 		params.goal = Goal.MIN_WAIT_TIME;
 		params.nearestRequestsLimit = 10;
 		params.nearestVehiclesLimit = 10;
-		params.cellSize = 1000.;
+		SquareGridZoneSystemParams zoneParams = (SquareGridZoneSystemParams) params.getZoneSystemParams();
+		zoneParams.cellSize = 1000.;
 		runBenchmark(false, params, utils);
 	}
 
@@ -56,7 +60,8 @@ void testRuleBased_minPickupTime() {
 		params.goal = Goal.MIN_PICKUP_TIME;
 		params.nearestRequestsLimit = 1;
 		params.nearestVehiclesLimit = 1;
-		params.cellSize = 100.;
+		SquareGridZoneSystemParams zoneParams = (SquareGridZoneSystemParams) params.getZoneSystemParams();
+		zoneParams.cellSize = 100.;
 		runBenchmark(false, params, utils);
 	}
 }
diff --git a/contribs/taxi/src/test/java/org/matsim/contrib/taxi/optimizer/zonal/ZonalTaxiOptimizerIT.java b/contribs/taxi/src/test/java/org/matsim/contrib/taxi/optimizer/zonal/ZonalTaxiOptimizerIT.java
index 575f26a4603..87de0493150 100644
--- a/contribs/taxi/src/test/java/org/matsim/contrib/taxi/optimizer/zonal/ZonalTaxiOptimizerIT.java
+++ b/contribs/taxi/src/test/java/org/matsim/contrib/taxi/optimizer/zonal/ZonalTaxiOptimizerIT.java
@@ -24,6 +24,7 @@
 
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
+import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystemParams;
 import org.matsim.contrib.taxi.optimizer.rules.RuleBasedRequestInserter.Goal;
 import org.matsim.contrib.taxi.optimizer.rules.RuleBasedTaxiOptimizerParams;
 import org.matsim.contrib.zone.ZonalSystemParams;
@@ -39,7 +40,8 @@ void testZonal_dse() {
 		rbParams.goal = Goal.DEMAND_SUPPLY_EQUIL;
 		rbParams.nearestRequestsLimit = 99999;
 		rbParams.nearestVehiclesLimit = 99999;
-		rbParams.cellSize = 99999.;
+		SquareGridZoneSystemParams zoneParams = (SquareGridZoneSystemParams) rbParams.getZoneSystemParams();
+		zoneParams.cellSize = 99999.;
 
 		ZonalSystemParams zsParams = new ZonalSystemParams();
 		zsParams.zonesShpFile = "zones/zones.shp";
@@ -59,7 +61,8 @@ void testZonal_minWaitTime() {
 		rbParams.goal = Goal.MIN_WAIT_TIME;
 		rbParams.nearestRequestsLimit = 10;
 		rbParams.nearestVehiclesLimit = 10;
-		rbParams.cellSize = 1000.;
+		SquareGridZoneSystemParams zoneParams = (SquareGridZoneSystemParams) rbParams.getZoneSystemParams();
+		zoneParams.cellSize = 1000.;
 
 		ZonalSystemParams zsParams = new ZonalSystemParams();
 		zsParams.zonesShpFile = "zones/zones.shp";
diff --git a/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testAssignment/output_events.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testAssignment/output_events.xml.gz
index 625f9d0073a..fa87157f1e8 100644
Binary files a/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testAssignment/output_events.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testAssignment/output_events.xml.gz differ
diff --git a/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testAssignment/output_plans.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testAssignment/output_plans.xml.gz
index d0b3f49d5b5..6926e4e0d13 100644
Binary files a/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testAssignment/output_plans.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testAssignment/output_plans.xml.gz differ
diff --git a/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testOneTaxi/output_plans.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testOneTaxi/output_plans.xml.gz
index a423bf11d2c..795113f0133 100644
Binary files a/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testOneTaxi/output_plans.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testOneTaxi/output_plans.xml.gz differ
diff --git a/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testRuleBased/output_events.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testRuleBased/output_events.xml.gz
index 625f9d0073a..fa87157f1e8 100644
Binary files a/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testRuleBased/output_events.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testRuleBased/output_events.xml.gz differ
diff --git a/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testRuleBased/output_plans.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testRuleBased/output_plans.xml.gz
index d0b3f49d5b5..6926e4e0d13 100644
Binary files a/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testRuleBased/output_plans.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/etaxi/run/RunETaxiScenarioIT/testRuleBased/output_plans.xml.gz differ
diff --git a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_arrivalTime/output_plans.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_arrivalTime/output_plans.xml.gz
index 7f3683fe733..0eabb684e27 100644
Binary files a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_arrivalTime/output_plans.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_arrivalTime/output_plans.xml.gz differ
diff --git a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_dse/output_plans.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_dse/output_plans.xml.gz
index c5960ee0794..98b20327ea6 100644
Binary files a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_dse/output_plans.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_dse/output_plans.xml.gz differ
diff --git a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_pickupTime/output_plans.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_pickupTime/output_plans.xml.gz
index ad3aea06944..195366627e9 100644
Binary files a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_pickupTime/output_plans.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_pickupTime/output_plans.xml.gz differ
diff --git a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_totalWaitTime/output_plans.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_totalWaitTime/output_plans.xml.gz
index eabe3b28dd2..55ecec4654d 100644
Binary files a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_totalWaitTime/output_plans.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/assignment/AssignmentTaxiOptimizerIT/testAssignment_totalWaitTime/output_plans.xml.gz differ
diff --git a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/fifo/FifoTaxiOptimizerIT/testFifo/output_plans.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/fifo/FifoTaxiOptimizerIT/testFifo/output_plans.xml.gz
index 4c532baa5c5..41a30561323 100644
Binary files a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/fifo/FifoTaxiOptimizerIT/testFifo/output_plans.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/fifo/FifoTaxiOptimizerIT/testFifo/output_plans.xml.gz differ
diff --git a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_dse/output_plans.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_dse/output_plans.xml.gz
index ed20dc628fc..6d434d623df 100644
Binary files a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_dse/output_plans.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_dse/output_plans.xml.gz differ
diff --git a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_minPickupTime/output_events.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_minPickupTime/output_events.xml.gz
index dc301f3138d..c74ecd38aba 100644
Binary files a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_minPickupTime/output_events.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_minPickupTime/output_events.xml.gz differ
diff --git a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_minPickupTime/output_plans.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_minPickupTime/output_plans.xml.gz
index 35560847305..0b5b8e4c2c9 100644
Binary files a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_minPickupTime/output_plans.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_minPickupTime/output_plans.xml.gz differ
diff --git a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_minWaitTime/output_plans.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_minWaitTime/output_plans.xml.gz
index e09d65480a9..8c1334661d2 100644
Binary files a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_minWaitTime/output_plans.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/rules/RuleBasedTaxiOptimizerIT/testRuleBased_minWaitTime/output_plans.xml.gz differ
diff --git a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/zonal/ZonalTaxiOptimizerIT/testZonal_dse/output_plans.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/zonal/ZonalTaxiOptimizerIT/testZonal_dse/output_plans.xml.gz
index fc1ffd599e5..f09eb9f894b 100644
Binary files a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/zonal/ZonalTaxiOptimizerIT/testZonal_dse/output_plans.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/zonal/ZonalTaxiOptimizerIT/testZonal_dse/output_plans.xml.gz differ
diff --git a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/zonal/ZonalTaxiOptimizerIT/testZonal_minWaitTime/output_plans.xml.gz b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/zonal/ZonalTaxiOptimizerIT/testZonal_minWaitTime/output_plans.xml.gz
index acddf468ac1..9df1e6b1a8a 100644
Binary files a/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/zonal/ZonalTaxiOptimizerIT/testZonal_minWaitTime/output_plans.xml.gz and b/contribs/taxi/test/input/org/matsim/contrib/taxi/optimizer/zonal/ZonalTaxiOptimizerIT/testZonal_minWaitTime/output_plans.xml.gz differ
diff --git a/contribs/vsp/src/test/java/org/matsim/integration/drtAndPt/PtAlongALine2Test.java b/contribs/vsp/src/test/java/org/matsim/integration/drtAndPt/PtAlongALine2Test.java
index 3a1e217d4f4..3e00dd03d50 100644
--- a/contribs/vsp/src/test/java/org/matsim/integration/drtAndPt/PtAlongALine2Test.java
+++ b/contribs/vsp/src/test/java/org/matsim/integration/drtAndPt/PtAlongALine2Test.java
@@ -23,6 +23,7 @@
 import org.matsim.api.core.v01.population.PlanElement;
 import org.matsim.api.core.v01.population.Population;
 import org.matsim.api.core.v01.population.PopulationFactory;
+import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystemParams;
 import org.matsim.contrib.drt.optimizer.insertion.extensive.ExtensiveInsertionSearchParams;
 import org.matsim.contrib.drt.routing.DrtRoute;
 import org.matsim.contrib.drt.routing.DrtRouteFactory;
@@ -35,6 +36,7 @@
 import org.matsim.contrib.dvrp.run.DvrpQSimComponents;
 import org.matsim.contrib.otfvis.OTFVisLiveModule;
 import org.matsim.core.config.Config;
+import org.matsim.core.config.ConfigGroup;
 import org.matsim.core.config.ConfigUtils;
 import org.matsim.core.config.groups.ScoringConfigGroup.ModeParams;
 import org.matsim.core.config.groups.RoutingConfigGroup;
@@ -220,6 +222,8 @@ void testPtAlongALineWithRaptorAndDrtServiceArea() {
 
 			DvrpConfigGroup dvrpConfig = ConfigUtils.addOrGetModule(config, DvrpConfigGroup.class);
 			dvrpConfig.networkModes = ImmutableSet.copyOf(Arrays.asList(TransportMode.drt, "drt2", "drt3"));
+			ConfigGroup zoneParams = dvrpConfig.getTravelTimeMatrixParams().createParameterSet(SquareGridZoneSystemParams.SET_NAME);
+			dvrpConfig.getTravelTimeMatrixParams().addParameterSet(zoneParams);
 
 			MultiModeDrtConfigGroup mm = ConfigUtils.addOrGetModule(config, MultiModeDrtConfigGroup.class);
 			{
diff --git a/contribs/vsp/src/test/java/playground/vsp/flowEfficiency/HierarchicalFLowEfficiencyCalculatorTest.java b/contribs/vsp/src/test/java/playground/vsp/flowEfficiency/HierarchicalFLowEfficiencyCalculatorTest.java
index 4e11d41f780..aaf0d796e91 100644
--- a/contribs/vsp/src/test/java/playground/vsp/flowEfficiency/HierarchicalFLowEfficiencyCalculatorTest.java
+++ b/contribs/vsp/src/test/java/playground/vsp/flowEfficiency/HierarchicalFLowEfficiencyCalculatorTest.java
@@ -15,6 +15,7 @@
 import org.matsim.api.core.v01.population.Person;
 import org.matsim.api.core.v01.population.Plan;
 import org.matsim.api.core.v01.population.PopulationFactory;
+import org.matsim.contrib.common.zones.systems.grid.square.SquareGridZoneSystemParams;
 import org.matsim.contrib.drt.routing.DrtRoute;
 import org.matsim.contrib.drt.routing.DrtRouteFactory;
 import org.matsim.contrib.drt.run.DrtConfigGroup;
@@ -27,6 +28,7 @@
 import org.matsim.contrib.dvrp.run.DvrpQSimComponents;
 import org.matsim.core.api.experimental.events.EventsManager;
 import org.matsim.core.config.Config;
+import org.matsim.core.config.ConfigGroup;
 import org.matsim.core.config.ConfigUtils;
 import org.matsim.core.controler.AbstractModule;
 import org.matsim.core.controler.Controler;
@@ -87,7 +89,11 @@ public void simulate(){
 		URL configUrl = IOUtils.extendUrl(ExamplesUtils.getTestScenarioURL("dvrp-grid"),
 				"eight_shared_taxi_config.xml");
 
-		Config config = ConfigUtils.loadConfig(configUrl, new DvrpConfigGroup(), new MultiModeDrtConfigGroup(), new OTFVisConfigGroup());
+		DvrpConfigGroup dvrpConfigGroup = new DvrpConfigGroup();
+		ConfigGroup zoneParams = dvrpConfigGroup.getTravelTimeMatrixParams().createParameterSet(SquareGridZoneSystemParams.SET_NAME);
+		dvrpConfigGroup.getTravelTimeMatrixParams().addParameterSet(zoneParams);
+
+		Config config = ConfigUtils.loadConfig(configUrl, dvrpConfigGroup, new MultiModeDrtConfigGroup(), new OTFVisConfigGroup());
 		config.controller().setOutputDirectory(utils.getOutputDirectory());
 		config.qsim().setEndTime(4*3600);
 
diff --git a/examples/scenarios/dvrp-grid/multi_mode_one_shared_taxi_config.xml b/examples/scenarios/dvrp-grid/multi_mode_one_shared_taxi_config.xml
index 4c218d03a23..bb0c4beed1f 100644
--- a/examples/scenarios/dvrp-grid/multi_mode_one_shared_taxi_config.xml
+++ b/examples/scenarios/dvrp-grid/multi_mode_one_shared_taxi_config.xml
@@ -3,7 +3,9 @@
 <config>
 	<module name="dvrp">
 		<parameterset type="travelTimeMatrix">
-			<param name="cellSize" value="10"/>
+			<parameterset type="SquareGridZoneSystem" >
+				<param name="cellSize" value="10"/>
+			</parameterset>
 		</parameterset>
 	</module>
 
diff --git a/examples/scenarios/dvrp-grid/one_shared_taxi_config.xml b/examples/scenarios/dvrp-grid/one_shared_taxi_config.xml
index 4e59438bbff..6f727d250e8 100644
--- a/examples/scenarios/dvrp-grid/one_shared_taxi_config.xml
+++ b/examples/scenarios/dvrp-grid/one_shared_taxi_config.xml
@@ -3,7 +3,9 @@
 <config>
 	<module name="dvrp">
 		<parameterset type="travelTimeMatrix">
-			<param name="cellSize" value="10"/>
+			<parameterset type="SquareGridZoneSystem" >
+				<param name="cellSize" value="10"/>
+			</parameterset>
 		</parameterset>
 	</module>
 
diff --git a/examples/scenarios/kelheim/config-with-drt.xml b/examples/scenarios/kelheim/config-with-drt.xml
index 76020e10903..7657d679502 100644
--- a/examples/scenarios/kelheim/config-with-drt.xml
+++ b/examples/scenarios/kelheim/config-with-drt.xml
@@ -267,11 +267,11 @@
             <!-- Writes out detailed DRT customer stats in each iteration. True by default. -->
             <param name="writeDetailedCustomerStats" value="true"/>
 
-            <parameterset type="zonalSystem">
-                <param name="zonesGeneration" value="ShapeFile"/>
-                <!--<param name="cellSize" value="200"/>-->
-                <param name="zonesShapeFile" value="drt-zones/drt-zonal-system.shp" />
-            </parameterset>
+			<parameterset type="zonalSystem" >
+				<parameterset type="GISFileZoneSystem" >
+					<param name="zonesShapeFile" value="drt-zones/drt-zonal-system.shp" />
+				</parameterset>
+			</parameterset>
 
             <!--<parameterset type="rebalancing">
                 <parameterset type="minCostFlowRebalancingStrategy">
@@ -312,11 +312,11 @@
             <!-- Writes out detailed DRT customer stats in each iteration. True by default. -->
             <param name="writeDetailedCustomerStats" value="true"/>
 
-            <parameterset type="zonalSystem">
-                <param name="zonesGeneration" value="ShapeFile"/>
-                <!--<param name="cellSize" value="200"/>-->
-                <param name="zonesShapeFile" value="drt-zones/drt-zonal-system.shp" />
-            </parameterset>
+			<parameterset type="zonalSystem" >
+				<parameterset type="GISFileZoneSystem" >
+					<param name="zonesShapeFile" value="drt-zones/drt-zonal-system.shp" />
+				</parameterset>
+			</parameterset>
 
             <!--<parameterset type="rebalancing">
                 <parameterset type="minCostFlowRebalancingStrategy">
@@ -334,7 +334,9 @@
         <!-- Used for estimation of travel times for VrpOptimizer by means of the exponential moving average. The weighting decrease, alpha, must be in (0,1]. We suggest small values of alpha, e.g. 0.05. The averaging starts from the initial travel time estimates. If not provided, the free-speed TTs is used as the initial estimates For more info see comments in: VrpTravelTimeEstimator, VrpTravelTimeModules, DvrpModule. -->
         <param name="travelTimeEstimationAlpha" value="0.05" />
         <parameterset type="travelTimeMatrix">
-            <param name="cellSize" value="200"/>
+			<parameterset type="SquareGridZoneSystem" >
+            	<param name="cellSize" value="200"/>
+			</parameterset>
         </parameterset>
 
     </module>
diff --git a/examples/scenarios/mielec/mielec_drt_config.xml b/examples/scenarios/mielec/mielec_drt_config.xml
index db2a8763ade..403207359f5 100644
--- a/examples/scenarios/mielec/mielec_drt_config.xml
+++ b/examples/scenarios/mielec/mielec_drt_config.xml
@@ -16,8 +16,9 @@
 			<!-- param name="vehiclesFile" value="vehicles-20-cap-2.xml" / -->
 
 			<parameterset type="zonalSystem">
-				<param name="zonesGeneration" value="GridFromNetwork"/>
-				<param name="cellSize" value="500"/>
+				<parameterset type="SquareGridZoneSystem" >
+					<param name="cellSize" value="500"/>
+				</parameterset>
 			</parameterset>
 
 			<parameterset type="rebalancing">
diff --git a/examples/scenarios/mielec/mielec_edrt_config.xml b/examples/scenarios/mielec/mielec_edrt_config.xml
index fdb3bd3c0f6..5ab35db759d 100644
--- a/examples/scenarios/mielec/mielec_edrt_config.xml
+++ b/examples/scenarios/mielec/mielec_edrt_config.xml
@@ -3,7 +3,9 @@
 <config>
 	<module name="dvrp">
 		<parameterset type="travelTimeMatrix">
-			<param name="cellSize" value="100"/>
+			<parameterset type="SquareGridZoneSystem" >
+				<param name="cellSize" value="100"/>
+			</parameterset>
 		</parameterset>
 	</module>
 
@@ -20,8 +22,9 @@
 			<!-- param name="vehiclesFile" value="vehicles-20-cap-2.xml" / -->
 
 			<parameterset type="zonalSystem">
-				<param name="zonesGeneration" value="GridFromNetwork"/>
-				<param name="cellSize" value="500"/>
+				<parameterset type="SquareGridZoneSystem" >
+					<param name="cellSize" value="500"/>
+				</parameterset>
 			</parameterset>
 
 			<parameterset type="rebalancing">
diff --git a/examples/scenarios/mielec/mielec_multiModeEdrt_config.xml b/examples/scenarios/mielec/mielec_multiModeEdrt_config.xml
index aa5d99b31a2..2d20b7b5756 100644
--- a/examples/scenarios/mielec/mielec_multiModeEdrt_config.xml
+++ b/examples/scenarios/mielec/mielec_multiModeEdrt_config.xml
@@ -3,7 +3,9 @@
 <config>
 	<module name="dvrp">
 		<parameterset type="travelTimeMatrix">
-			<param name="cellSize" value="100"/>
+			<parameterset type="SquareGridZoneSystem" >
+				<param name="cellSize" value="100"/>
+			</parameterset>
 		</parameterset>
 	</module>
 
@@ -21,8 +23,9 @@
 			<!-- param name="vehiclesFile" value="vehicles-20-cap-2.xml" / -->
 
 			<parameterset type="zonalSystem">
-				<param name="zonesGeneration" value="GridFromNetwork"/>
-				<param name="cellSize" value="500"/>
+				<parameterset type="SquareGridZoneSystem" >
+					<param name="cellSize" value="500"/>
+				</parameterset>
 			</parameterset>
 
 			<parameterset type="rebalancing">
@@ -46,8 +49,9 @@
 			<!-- param name="vehiclesFile" value="vehicles-20-cap-2.xml" / -->
 
 			<parameterset type="zonalSystem">
-				<param name="zonesGeneration" value="GridFromNetwork"/>
-				<param name="cellSize" value="500"/>
+				<parameterset type="SquareGridZoneSystem" >
+					<param name="cellSize" value="500"/>
+				</parameterset>
 			</parameterset>
 
 			<parameterset type="rebalancing">
diff --git a/examples/scenarios/mielec/mielec_serviceArea_based_drt_config.xml b/examples/scenarios/mielec/mielec_serviceArea_based_drt_config.xml
index d2bdd9305f7..ceb0ca0a3cb 100644
--- a/examples/scenarios/mielec/mielec_serviceArea_based_drt_config.xml
+++ b/examples/scenarios/mielec/mielec_serviceArea_based_drt_config.xml
@@ -3,7 +3,9 @@
 <config>
 	<module name="dvrp">
 		<parameterset type="travelTimeMatrix">
-			<param name="cellSize" value="100"/>
+			<parameterset type="SquareGridZoneSystem" >
+				<param name="cellSize" value="100"/>
+			</parameterset>
 		</parameterset>
 	</module>
 
@@ -22,8 +24,9 @@
 			<!-- param name="vehiclesFile" value="vehicles-20-cap-2.xml" / -->
 
 			<parameterset type="zonalSystem">
-				<param name="zonesGeneration" value="GridFromNetwork"/>
-				<param name="cellSize" value="500"/>
+				<parameterset type="SquareGridZoneSystem" >
+					<param name="cellSize" value="500"/>
+				</parameterset>
 			</parameterset>
 
 			<parameterset type="rebalancing">
diff --git a/examples/scenarios/mielec/mielec_stop_based_drt_config.xml b/examples/scenarios/mielec/mielec_stop_based_drt_config.xml
index dd3df82fa60..5b9da8ef8d0 100644
--- a/examples/scenarios/mielec/mielec_stop_based_drt_config.xml
+++ b/examples/scenarios/mielec/mielec_stop_based_drt_config.xml
@@ -3,7 +3,9 @@
 <config>
 	<module name="dvrp">
 		<parameterset type="travelTimeMatrix">
-			<param name="cellSize" value="100"/>
+			<parameterset type="SquareGridZoneSystem" >
+				<param name="cellSize" value="100"/>
+			</parameterset>
 		</parameterset>
 	</module>
 
@@ -26,8 +28,9 @@
 			<!-- param name="vehiclesFile" value="vehicles-20-cap-2.xml" / -->
 
 			<parameterset type="zonalSystem">
-				<param name="zonesGeneration" value="GridFromNetwork"/>
-				<param name="cellSize" value="500"/>
+				<parameterset type="SquareGridZoneSystem" >
+					<param name="cellSize" value="500"/>
+				</parameterset>
 			</parameterset>
 
 			<parameterset type="rebalancing">
diff --git a/matsim/src/main/java/org/matsim/core/population/routes/PopulationComparison.java b/matsim/src/main/java/org/matsim/core/population/routes/PopulationComparison.java
index d6cc75e3923..8dac954aa3f 100644
--- a/matsim/src/main/java/org/matsim/core/population/routes/PopulationComparison.java
+++ b/matsim/src/main/java/org/matsim/core/population/routes/PopulationComparison.java
@@ -2,12 +2,10 @@
 
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
-import org.matsim.api.core.v01.population.Person;
-import org.matsim.api.core.v01.population.Plan;
-import org.matsim.api.core.v01.population.PlanElement;
-import org.matsim.api.core.v01.population.Population;
+import org.matsim.api.core.v01.population.*;
 
 import java.util.Iterator;
+import java.util.List;
 
 public class PopulationComparison{
 	public enum Result { equal, notEqual }
@@ -39,7 +37,8 @@ public Result compare( Population population1, Population population2 ){
 			}
 			Plan plan1 = person1.getSelectedPlan();
 			Plan plan2 = person2.getSelectedPlan();
-			if ( Math.abs( plan1.getScore() - plan2.getScore() ) > 100.*Double.MIN_VALUE  ) {
+			if ( Math.abs( plan1.getScore() - plan2.getScore() ) > 100.*Double.MIN_VALUE ||
+			 !equals(plan1.getPlanElements(), plan2.getPlanElements())) {
 
 				double maxScore = Double.NEGATIVE_INFINITY;
 				for( Plan plan : person2.getPlans() ){
@@ -61,10 +60,70 @@ public Result compare( Population population1, Population population2 ){
 				log.warn( "" );
 
 			}
-
 		}
+		return result ;
+	}
 
 
-		return result ;
+	public static boolean equals(List<PlanElement> planElements,
+								 List<PlanElement> planElements2) {
+		int nElements = planElements.size();
+		if (nElements != planElements2.size()) {
+			return false;
+		} else {
+			for (int i = 0; i < nElements; i++) {
+				if (!equals(planElements.get(i), planElements2.get(i))) {
+					return false;
+				}
+			}
+		}
+		return true;
+	}
+
+	/* Warning: This is NOT claimed to be correct. (It isn't.)
+	 *
+	 */
+	private static boolean equals(PlanElement o1, PlanElement o2) {
+		if (o1 instanceof Leg) {
+			if (o2 instanceof Leg) {
+				Leg leg1 = (Leg) o1;
+				Leg leg2 = (Leg) o2;
+				if (!leg1.getDepartureTime().equals(leg2.getDepartureTime())) {
+					return false;
+				}
+				if (!leg1.getMode().equals(leg2.getMode())) {
+					return false;
+				}
+				if (!leg1.getTravelTime().equals(leg2.getTravelTime())) {
+					return false;
+				}
+			} else {
+				return false;
+			}
+		} else if (o1 instanceof Activity) {
+			if (o2 instanceof Activity) {
+				Activity activity1 = (Activity) o1;
+				Activity activity2 = (Activity) o2;
+				if (activity1.getEndTime().isUndefined() ^ activity2.getEndTime().isUndefined()) {
+					return false;
+				}
+				if (activity1.getEndTime().isDefined() && activity1.getEndTime().seconds()
+					!= activity2.getEndTime().seconds()) {
+					return false;
+				}
+				if (activity1.getStartTime().isUndefined() ^ activity2.getStartTime().isUndefined()) {
+					return false;
+				}
+				if (activity1.getStartTime().isDefined() && activity1.getStartTime().seconds()
+					!= activity2.getStartTime().seconds()) {
+					return false;
+				}
+			} else {
+				return false;
+			}
+		} else {
+			throw new RuntimeException ("Unexpected PlanElement");
+		}
+		return true;
 	}
 }
diff --git a/matsim/src/main/java/org/matsim/utils/gis/shp2matsim/ShpGeometryUtils.java b/matsim/src/main/java/org/matsim/utils/gis/shp2matsim/ShpGeometryUtils.java
index 4c5d56e9f98..77cc026e3f7 100644
--- a/matsim/src/main/java/org/matsim/utils/gis/shp2matsim/ShpGeometryUtils.java
+++ b/matsim/src/main/java/org/matsim/utils/gis/shp2matsim/ShpGeometryUtils.java
@@ -25,8 +25,10 @@
 
 import org.locationtech.jts.geom.Geometry;
 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.PreparedGeometryFactory;
+import org.locationtech.jts.geom.prep.PreparedPolygon;
 import org.matsim.api.core.v01.Coord;
 import org.matsim.core.utils.geometry.geotools.MGC;
 import org.matsim.core.utils.gis.GeoFileReader;
@@ -47,6 +49,13 @@ public static List<PreparedGeometry> loadPreparedGeometries(URL url) {
 				.collect(Collectors.toList());
 	}
 
+	public static List<PreparedPolygon> loadPreparedPolygons(URL url) {
+		return GeoFileReader.getAllFeatures(url)
+			.stream()
+			.map(sf -> new PreparedPolygon((Polygonal)sf.getDefaultGeometry()))
+			.collect(Collectors.toList());
+	}
+
 	public static boolean isCoordInGeometries(Coord coord, List<Geometry> geometries) {
 		Point point = MGC.coord2Point(coord);
 		return geometries.stream().anyMatch(g -> g.contains(point));
diff --git a/matsim/src/test/java/org/matsim/population/algorithms/ChooseRandomLegModeForSubtourTest.java b/matsim/src/test/java/org/matsim/population/algorithms/ChooseRandomLegModeForSubtourTest.java
index 93986fe0404..0bb065a1ee5 100644
--- a/matsim/src/test/java/org/matsim/population/algorithms/ChooseRandomLegModeForSubtourTest.java
+++ b/matsim/src/test/java/org/matsim/population/algorithms/ChooseRandomLegModeForSubtourTest.java
@@ -46,6 +46,7 @@
 import org.matsim.core.population.PopulationUtils;
 import org.matsim.core.population.algorithms.ChooseRandomLegModeForSubtour;
 import org.matsim.core.population.algorithms.PermissibleModesCalculator;
+import org.matsim.core.population.routes.PopulationComparison;
 import org.matsim.core.replanning.modules.SubtourModeChoice;
 import org.matsim.core.router.MainModeIdentifierImpl;
 import org.matsim.core.router.TripStructureUtils;
@@ -291,7 +292,7 @@ private void testSubTourMutationToCar(Network network, double probaForRandomSing
 			Plan plan = createPlan(network, activityChainString, originalMode);
 			Plan originalPlan = PopulationUtils.createPlan(person);
 			PopulationUtils.copyFromTo(plan, originalPlan);
-			assertTrue(TestsUtil.equals(plan.getPlanElements(), originalPlan.getPlanElements()));
+			assertTrue(PopulationComparison.equals(plan.getPlanElements(), originalPlan.getPlanElements()));
 			testee.run(plan);
 			assertSubTourMutated(plan, originalPlan, expectedMode, false);
 		}
@@ -307,7 +308,7 @@ private void testSubTourMutationToCar(ActivityFacilities facilities, double prob
 			Plan plan = createPlan(facilities, activityChainString, originalMode);
 			Plan originalPlan = PopulationUtils.createPlan(person);
 			PopulationUtils.copyFromTo(plan, originalPlan);
-			assertTrue(TestsUtil.equals(plan.getPlanElements(), originalPlan.getPlanElements()));
+			assertTrue(PopulationComparison.equals(plan.getPlanElements(), originalPlan.getPlanElements()));
 			testee.run(plan);
 			assertSubTourMutated(plan, originalPlan, expectedMode, true);
 		}
@@ -322,9 +323,9 @@ private void testUnknownModeDoesntMutate(Network network, double probaForRandomS
 			Plan plan = createPlan(network, activityChainString, originalMode);
 			Plan originalPlan = PopulationUtils.createPlan(person);
 			PopulationUtils.copyFromTo(plan, originalPlan);
-			assertTrue(TestsUtil.equals(plan.getPlanElements(), originalPlan.getPlanElements()));
+			assertTrue(PopulationComparison.equals(plan.getPlanElements(), originalPlan.getPlanElements()));
 			testee.run(plan);
-			assertTrue(TestsUtil.equals(plan.getPlanElements(), originalPlan.getPlanElements()));
+			assertTrue(PopulationComparison.equals(plan.getPlanElements(), originalPlan.getPlanElements()));
 		}
 	}
 
@@ -337,9 +338,9 @@ private void testUnknownModeDoesntMutate(ActivityFacilities facilities, double p
 			Plan plan = createPlan(facilities, activityChainString, originalMode);
 			Plan originalPlan = PopulationUtils.createPlan(person);
 			PopulationUtils.copyFromTo(plan, originalPlan);
-			assertTrue(TestsUtil.equals(plan.getPlanElements(), originalPlan.getPlanElements()));
+			assertTrue(PopulationComparison.equals(plan.getPlanElements(), originalPlan.getPlanElements()));
 			testee.run(plan);
-			assertTrue(TestsUtil.equals(plan.getPlanElements(), originalPlan.getPlanElements()));
+			assertTrue(PopulationComparison.equals(plan.getPlanElements(), originalPlan.getPlanElements()));
 		}
 	}
 
@@ -353,7 +354,7 @@ private void testSubTourMutationToPt(ActivityFacilities facilities, double proba
 			Plan plan = createPlan(facilities, activityChainString, originalMode);
 			Plan originalPlan = PopulationUtils.createPlan(person);
 			PopulationUtils.copyFromTo(plan, originalPlan);
-			assertTrue(TestsUtil.equals(plan.getPlanElements(), originalPlan.getPlanElements()));
+			assertTrue(PopulationComparison.equals(plan.getPlanElements(), originalPlan.getPlanElements()));
 			testee.run(plan);
 			assertSubTourMutated(plan, originalPlan, expectedMode, true);
 		}
@@ -369,7 +370,7 @@ private void testSubTourMutationToPt(Network network, double probaForRandomSingl
 			Plan plan = createPlan(network, activityChainString, originalMode);
 			Plan originalPlan = PopulationUtils.createPlan(person);
 			PopulationUtils.copyFromTo(plan, originalPlan);
-			assertTrue(TestsUtil.equals(plan.getPlanElements(), originalPlan.getPlanElements()));
+			assertTrue(PopulationComparison.equals(plan.getPlanElements(), originalPlan.getPlanElements()));
 			testee.run(plan);
 			assertSubTourMutated(plan, originalPlan, expectedMode, false);
 		}
diff --git a/matsim/src/test/java/org/matsim/population/algorithms/TestsUtil.java b/matsim/src/test/java/org/matsim/population/algorithms/TestsUtil.java
index 2c18cc6e194..34364d60d6f 100644
--- a/matsim/src/test/java/org/matsim/population/algorithms/TestsUtil.java
+++ b/matsim/src/test/java/org/matsim/population/algorithms/TestsUtil.java
@@ -68,66 +68,5 @@ static Plan createPlanFromLinks(Network layer, Person person, String mode, Strin
 		return plan;
 	}
 
-	/* Warning: This is NOT claimed to be correct. (It isn't.)
-	 *
-	 */
-	static boolean equals(PlanElement o1, PlanElement o2) {
-		if (o1 instanceof Leg) {
-			if (o2 instanceof Leg) {
-				Leg leg1 = (Leg) o1;
-				Leg leg2 = (Leg) o2;
-				if (!leg1.getDepartureTime().equals(leg2.getDepartureTime())) {
-					return false;
-				}
-				if (!leg1.getMode().equals(leg2.getMode())) {
-					return false;
-				}
-				if (!leg1.getTravelTime().equals(leg2.getTravelTime())) {
-					return false;
-				}
-			} else {
-				return false;
-			}
-		} else if (o1 instanceof Activity) {
-			if (o2 instanceof Activity) {
-				Activity activity1 = (Activity) o1;
-				Activity activity2 = (Activity) o2;
-				if (activity1.getEndTime().isUndefined() ^ activity2.getEndTime().isUndefined()) {
-					return false;
-				}
-				if (activity1.getEndTime().isDefined() && activity1.getEndTime().seconds()
-						!= activity2.getEndTime().seconds()) {
-					return false;
-				}
-				if (activity1.getStartTime().isUndefined() ^ activity2.getStartTime().isUndefined()) {
-					return false;
-				}
-				if (activity1.getStartTime().isDefined() && activity1.getStartTime().seconds()
-						!= activity2.getStartTime().seconds()) {
-					return false;
-				}
-			} else {
-				return false;
-			}
-		} else {
-			throw new RuntimeException ("Unexpected PlanElement");
-		}
-		return true;
-	}
-
-	public static boolean equals(List<PlanElement> planElements,
-			List<PlanElement> planElements2) {
-		int nElements = planElements.size();
-		if (nElements != planElements2.size()) {
-			return false;
-		} else {
-			for (int i = 0; i < nElements; i++) {
-				if (!equals(planElements.get(i), planElements2.get(i))) {
-					return false;
-				}
-			}
-		}
-		return true;
-	}
 
 }