Skip to content

7. In depth look at districts

Milan Ender edited this page Aug 22, 2016 · 6 revisions

This page mostly refers to the DistrictFacetComponent.
Districts divide the city into distinct areas, providing a more diverse and interesting city layout. They contain a list of zones so that only a distinct pool of buildings can spawn in one district.
The process of district creation involves two steps:


  1. Divide the area into distinct clusters using K-Means clustering.
  2. Map the clusters to district types while preserving correlation to the demands of zones of a culture.

Clustering

Using the K-means algorithm (namely Lloyd's algorithm) with the positions and some random numbers with a low weight as input vectors, distinct clusters are created. I wrote my own implementation but any other would be likewise fitting.

Mapping of district types

The different district types are currently assigned by having a probability distribution p(x) determined by the needs of BuildingNeedsPerZone values of the culture and the outstanding demand for each zone area. As a district can contain several zones, each zone counts evenly to the total district area. For example, if you have a district with zone A and zone B of size S, the total area of zone A and B will be S/2 bigger. If the demand for a zone is exceeded, the probability is set to 0 for that district.
The relevant code explains how p(x) is calculated probably better than I can:

                Map<DistrictType, Float> probabilites = new HashMap<>(districtManager.getDistrictTypes().size());
                float totalDiff = 0;

                for (DistrictType districtType : districtManager.getDistrictTypes()) {
                    float diff = 0;
                    Map<String, Float> tempZoneArea = new HashMap<>(zoneArea);
                    for (String zone : districtType.zones) {
                        float area = districtSize.get(i) / districtType.zones.size();
                        tempZoneArea.put(zone, tempZoneArea.getOrDefault(zone, 0f) + area);
                        if (!culturalNeedsPercentage.containsKey(zone)) {
                            diff = Float.MAX_VALUE;
                        }
                        else if (tempZoneArea.get(zone) / totalAssignedArea > culturalNeedsPercentage.get(zone)) {
                            diff = 9999999f;
                        } else {
                            diff += TeraMath.fastAbs(tempZoneArea.get(zone) / totalAssignedArea - culturalNeedsPercentage.get(zone));
                        }
                    }

                    diff = (diff == 0) ? 0 : 1 / diff;
                    probabilites.put(districtType, diff);
                    totalDiff += diff;
                }
                for (DistrictType districtType : districtManager.getDistrictTypes()) {
                    probabilites.put(districtType, probabilites.getOrDefault(districtType, 0f) / totalDiff);
                }

afterward a random value x is applied to p(x) which will get us the relevant district.
Although I haven't made a statistical analysis of that mapping, samples showed a pretty nice consistence with the BuildingNeedsPerZone with negligible deviations. After all it isn't something immediately visible and the random size of the clusters don't allow 100% precision.