ZoneCodes and territory management

ZoneCodes are the territory model of JOpt.TourOptimizer. This guide shows how to set them on WorkingHours and on nodes, in Java and in the JSON input of the REST service, and when to make them hard or soft. It is for developers who model service districts, postal areas or other territories. It assumes the nodes, resources and WorkingHours of Basic elements.
What it does
Technicians, drivers and sales representatives often serve only their own territory, such as a service district, a set of postal districts or a region, and a territory can change from one day to the next. A node near a territory border can belong to more than one zone, and the optimizer then decides which resource serves it.
ZoneCodes function like regional postcodes: they define which resources can visit which nodes, on which days, and under what conditions. Importantly, ZoneCodes are not necessarily geographical. They can be freely defined to represent any grouping: service districts, customer segments, skill categories, contract types, or organizational units.
To our best knowledge, among the routing engines we checked (Google OR-Tools, Timefold, jsprit, VROOM, HERE Tour Planning, PTV), only JOpt combines zone codes that are labels (numbers or UK postcodes) rather than map polygons, switch per shift and are hard or soft, with nodes that belong to several zones at once. To our best knowledge, Timefold Field Service Routing sets map areas per shift, and in HERE Tour Planning a job can carry several territories, while territories are set per vehicle type.
Resource side and node side
ZoneCodes have two sides:
- Resource side: constraints on WorkingHours. The WorkingHours of a resource carry ZoneCode constraints. They define which zones the resource may serve during that shift, and different shifts can carry different zones. A territory that changes from one day to the next is then a data update, not a model change.
- Node side: qualifications. A node carries one or more ZoneCode qualifications that declare the zones it belongs to. A node with several zone qualifications can go to any resource whose shift covers at least one of those zones.
The optimizer matches the qualifications of a node against the constraints of a shift. If they match, the resource may visit the node during that shift. If they do not match, the result depends on whether the constraint is hard or soft (see Hard and soft constraints).
A typical ZoneCode setup: resources are assigned to zones through constraints on their WorkingHours, nodes carry zone qualifications, and border nodes belong to more than one zone.
ZoneCode types
JOpt has these ZoneCode types. They work the same way: constraints on WorkingHours, qualifications on nodes, hard or soft enforcement.
| Type | Classes | Typical use |
|---|---|---|
| ZoneNumber | ZoneNumber, ZoneNumberQualification, ZoneNumberConstraint | Numeric territory ids (district 1, district 2), for abstract zones, service areas and on-call regions |
| UKPostCode | UKPostCode, UKPostCodeQualification, UKPostCodeConstraint | UK-style postcode identifiers (B37, B48), when territories map directly to postal codes or ZIP codes |
Choose the type by the territory identifiers you already have: postcodes if your dispatch system uses postal codes, ZoneNumbers if it uses numeric region ids.
When to use it
ZoneCodes fit these cases:
- Mandatory territories, such as regulatory boundaries or contractual service areas: hard constraints.
- Preferred territories where cross-coverage is acceptable but should stay rare: soft constraints.
- Territories that change by day or by shift, such as rotating coverage, on-call schedules and territory handovers.
- Nodes on the border between territories, which either neighboring territory may serve.
ZoneCodes and zone crossing penalization
ZoneCodes serve two separate purposes that work together:
- Territory enforcement (this guide): which resources may visit which nodes, per shift.
- Crossing penalization (the zone crossing penalization guide): a cost when a route crosses from one zone into another, which discourages unnecessary boundary transitions.
You can enforce territories with hard ZoneCode constraints and still penalize crossings between allowed adjacent zones. For example, a resource may serve Zone 1 and Zone 2 (territory enforcement), but switching between them during a single route is penalized (crossing penalization). The routes then respect the territories and cross bridges or tunnels less often.
How to use it
Per-shift territories on WorkingHours
ZoneCode constraints are attached to individual WorkingHours, not to the resource as a whole, so the territory can differ per shift. A resource with two WorkingHours (two shifts or two days) can cover different zones on each:
// Day 1: resource covers Zone 1 and Zone 2
ZoneNumberConstraint constraintDay1 = new ZoneNumberConstraint();
constraintDay1.setIsHard(true);
constraintDay1.addZoneCode(zoneOne);
constraintDay1.addZoneCode(zoneTwo);
woh1.addConstraint(constraintDay1);
// Day 2: resource covers Zone 3 only
ZoneNumberConstraint constraintDay2 = new ZoneNumberConstraint();
constraintDay2.setIsHard(true);
constraintDay2.addZoneCode(zoneThree);
woh2.addConstraint(constraintDay2);
On day 1, the resource can visit nodes in Zone 1 or Zone 2. On day 2, the same resource can only visit nodes in Zone 3. Rotating coverage, on-call schedules and territory handovers become a data change in the WorkingHours definition.
One WorkingHours can carry several ZoneCodes, so a resource can cover several zones within one shift, for example adjacent territories or backup coverage for more than one district.
Nodes in more than one zone
A node can belong to more than one zone at the same time. One way is to add extra codes to the qualification:
// This node belongs to both Zone 1 and Zone 2
ZoneNumberQualification borderQuali = new ZoneNumberQualification(zoneOne);
borderQuali.addExtraCode(zoneTwo);
borderNode.addQualification(borderQuali);
ZoneNumberConstraintExample does the same with two qualifications on the node Oberhausen-Z1-Z2, one for Zone 1 and one for Zone 2.
This is the recommended strategy for territory borders. Instead of forcing a binary decision ("this node is in Zone 1, not Zone 2"), you declare that the node sits on the boundary and let the optimizer decide which resource assignment yields the best overall plan.
In practice, this is valuable for:
- Nodes near the border of two service districts
- Customers served by multiple depots
- Locations accessible from different geographic areas (e.g. both sides of a highway)
The optimizer evaluates all eligible resource/shift combinations and selects the one that minimizes total cost while respecting all other constraints.
Hard and soft constraints
Each ZoneCode constraint is either hard or soft. A hard constraint:
constraint.setIsHard(true);
With a hard constraint, the resource may not visit nodes outside its zones during this shift, and the optimizer does not build a solution that violates it. Use it for mandatory territory rules, regulatory boundaries or contractual service areas.
A soft constraint:
constraint.setIsHard(false);
With a soft constraint, the resource prefers its zones but can serve nodes outside them at a penalty cost. The property JOptWeight.ZoneCode sets the weight of that penalty; see Optimization properties. Use soft constraints for preferred territories where cross-coverage is acceptable but should stay rare. The value in this snippet is a sample:
props.setProperty("JOptWeight.ZoneCode", "10.0");
Hard or soft is set per constraint, and each constraint belongs to one WorkingHours. The same resource can have hard zones on Monday and soft zones on Tuesday if the operational rules differ by day.
Resources without a ZoneCode constraint
If the WorkingHours of a resource carry no ZoneCode constraint, the resource may visit all nodes, whatever their zone qualifications. This allows a gradual introduction of territories:
- Start by defining zones for nodes only. This has no effect on territories yet, because no resource has a constraint.
- Then add constraints to specific resources or specific shifts.
- Keep "floating" resources without constraints for emergency coverage or overflow.
Example: numbered zones with daily switching
In ZoneNumberConstraintExample.java, a resource named Jack has two WorkingHours (two days). On day 1 he may visit Zone 1 and Zone 2; on day 2 he switches to Zone 3 only. The nodes are qualified with their zones, and the border node Oberhausen-Z1-Z2 carries qualifications for both Zone 1 and Zone 2, so it can be served on day 1 through either zone. The code below is a condensed version of the zone parts; it qualifies the border node with an extra code:
// Define zones
ZoneNumber zoneOne = new ZoneNumber(1);
ZoneNumber zoneTwo = new ZoneNumber(2);
ZoneNumber zoneThree = new ZoneNumber(3);
// Day 1: Zone 1 + Zone 2
ZoneNumberConstraint constraintWOH1 = new ZoneNumberConstraint();
constraintWOH1.setIsHard(true);
constraintWOH1.addZoneCode(zoneOne);
constraintWOH1.addZoneCode(zoneTwo);
woh1.addConstraint(constraintWOH1);
// Day 2: Zone 3 only
ZoneNumberConstraint constraintWOH2 = new ZoneNumberConstraint();
constraintWOH2.setIsHard(true);
constraintWOH2.addZoneCode(zoneThree);
woh2.addConstraint(constraintWOH2);
// Border node: belongs to Zone 1 and Zone 2
ZoneNumberQualification borderQuali = new ZoneNumberQualification(zoneOne);
borderQuali.addExtraCode(zoneTwo);
oberhausen.addQualification(borderQuali);
With these hard constraints, Jack visits only Zone 1 and Zone 2 nodes on day 1 and only Zone 3 nodes on day 2.
Example: UK postcodes as ZoneCodes
UKPostCodeExample.java uses UK postcode identifiers as zones. The resource covers the postcodes B37 and B48 on day 1 and B36 on day 2. The nodes carry postcode qualifications such as B37 2G and B48 2F. The node KoelnNoMatchingPostCode matches none of the resource's postcodes, so under the hard constraints it is not assigned.
This pattern fits territories that map to postal codes, ZIP codes or service regions in a CRM. No spatial polygons are needed: ZoneCodes reuse the identifiers that your dispatch system already maintains.
JSON input for the REST service
In the JSON input of the REST service, a zone constraint is an entry of workingHoursConstraints on a WorkingHours, with isHard and a type whose typeName is ZoneNumberConstraint or UKPostCodeConstraint and whose zoneCodes list the zones. A node lists its zone qualifications in qualifications, each with a type whose typeName is ZoneNumberQualification or UKPostCodeQualification. The API reference shows the full schema.
Modeling guidance
Match the granularity to dispatch decisions. If dispatch works by city districts, use district ids. If it works by coarse regions, use coarse zone numbers. Zones that are too fine create unnecessary constraints.
Use hard enforcement for regulatory and contractual boundaries. If a technician legally cannot work outside a region, use hard. If the region is only preferred, use soft.
Model border nodes with multiple qualifications. This avoids arbitrary assignments near territory edges and lets the optimizer choose the plan.
Use per-WorkingHours constraints for daily rotation. "Territories change on Monday" becomes a data update in the WorkingHours definition, not a model change.
Keep floating resources for flexibility. Resources without ZoneCode constraints can visit all zones. Use them for emergency coverage, overflow, or senior technicians who cover the entire service area.
Properties
| Property key | What it sets |
|---|---|
JOptWeight.ZoneCode | The weight of the penalty when a soft ZoneCode constraint is violated |
The default and the allowed values are on Optimization properties. The properties of zone crossing penalization are in the zone crossing penalization guide.
Limits
- A hard constraint can leave a node unassigned. In the UK postcode example,
KoelnNoMatchingPostCodematches no postcode of the resource's shifts and is not assigned. - A soft constraint is a preference. A resource can still serve nodes outside its zones, and each such visit adds a penalty weighted by
JOptWeight.ZoneCode.
Related examples
- ZoneCodes example: the ZoneNumber and UK postcode examples explained, with design patterns and best practices
- ZoneNumberConstraintExample.java: numbered zones with daily switching and a border node, on GitHub
- UKPostCodeExample.java: UK postcodes as ZoneCodes, on GitHub
Related pages
- Zone crossing penalization: a cost for route steps from one zone into another, also per direction
- Special features: defining territories via ZoneCodes: the short overview of ZoneCodes
- Resources: WorkingHours and the other settings of a resource
- Optimization properties: the property keys by category, with their defaults
Authors
DNA Evolutions GmbH
Added: 2026-03-29 (JOpt core 7.5.3-j17) | Last updated: 2026-10-21
Skill with expertise and cost model
How the cost models of a skill condition with expertise levels decide between resources whose level differs from the level a node requires.
Zone crossing penalization
How JOpt adds a cost when a route moves from one zone into another, with a global multiplier and optional multipliers per direction.