def reorganizeAtomicBlockBetweenRedistrictingGroups(redistrictingGroups): for redistrictingGroup in redistrictingGroups: for borderBlock in redistrictingGroup.borderChildren: borderBlock.removeNonIntersectingNeighbors() atomicBlockGroupDict = {} for redistrictingGroup in redistrictingGroups: atomicBlockGroupDict[redistrictingGroup.graphId] = redistrictingGroup.children.copy() atomicBlockGroups = atomicBlockGroupDict.values() for atomicBlockGroup in atomicBlockGroups: contiguousRegions = findContiguousGroupsOfGraphObjects(atomicBlockGroup) while len(contiguousRegions) > 1: smallestContiguousRegion = min(contiguousRegions, key=lambda contiguousRegion: len(contiguousRegion)) smallestContiguousRegionPolygon = polygonFromMultipleGeometries(smallestContiguousRegion) otherRegion = None otherSplitChildrenList = [x for x in atomicBlockGroups if x is not atomicBlockGroup] for otherSplitChildren in otherSplitChildrenList: otherSplitChildrenPolygon = polygonFromMultipleGeometries(otherSplitChildren) if intersectingPolygons(smallestContiguousRegionPolygon, otherSplitChildrenPolygon): otherRegion = otherSplitChildren break if otherRegion is None: allBlocksInOtherSplits = [block for blockList in otherSplitChildrenList for block in blockList] closestBlock = findClosestGeometry(smallestContiguousRegionPolygon, allBlocksInOtherSplits) closestSplit = next((blockList for blockList in otherSplitChildrenList if closestBlock in blockList), None) otherRegion = closestSplit for childBlock in smallestContiguousRegion: atomicBlockGroup.remove(childBlock) childBlock.removeNeighborConnections() otherRegion.append(childBlock) assignNeighborBlocksFromCandidateBlocks(block=childBlock, candidateBlocks=otherRegion) contiguousRegions = findContiguousGroupsOfGraphObjects(atomicBlockGroup) for key, value in atomicBlockGroupDict.items(): groupWithId = next((redistrictingGroup for redistrictingGroup in redistrictingGroups if redistrictingGroup.graphId == key), None) groupWithId.children = value for redistrictingGroup in redistrictingGroups: redistrictingGroup.attachOrphanBlocksToClosestNeighbor() return redistrictingGroups
def polsbyPopperScoreOfCombinedGeometry(currentGroupPolygon, remainingGroups, candidateGroups, fastCalculations=True): candidateGroupsPolygon = polygonFromMultipleGeometries( candidateGroups, useEnvelope=fastCalculations) # never useEnvelope here, because currentGroupPolygon is our cached shape candidatePolygon = polygonFromMultiplePolygons( [currentGroupPolygon, candidateGroupsPolygon]) combinedRemainingPolygon = polygonFromMultipleGeometries( remainingGroups, useEnvelope=fastCalculations) score = polsbyPopperScoreOfPolygon(candidatePolygon) remainingScore = polsbyPopperScoreOfPolygon( combinedRemainingPolygon) minimumPolsbyPopperScore = min(score, remainingScore) return minimumPolsbyPopperScore
def distanceScoreOfCombinedGeometry(currentGroupPolygon, remainingGroups, candidateGroups, fastCalculations=True): candidateGroupsPolygon = polygonFromMultipleGeometries( candidateGroups, useEnvelope=fastCalculations) distance = currentGroupPolygon.centroid.distance( candidateGroupsPolygon.centroid) score = 1 / distance return score
def cardinalDirectionScoreOfCandidateGroups( currentGroupPolygon, remainingGroups, candidateGroups, fastCalculations=True): boundsIndex = boundsIndexFromDirection(fillOriginDirection) directionReferenceValue = self.geometry.bounds[boundsIndex] candidateGroupsPolygon = polygonFromMultipleGeometries( candidateGroups, useEnvelope=fastCalculations) candidateGroupsValue = candidateGroupsPolygon.bounds[ boundsIndex] difference = directionReferenceValue - candidateGroupsValue difference = math.fabs(difference) if difference == 0: score = math.inf else: score = 1 / difference return score
def getPopulationEnergyPolygonSplit(self, alignment, shouldDrawGraph=False): finishingBlocksToAvoid = [] while True: lowestEnergySeamResult = self.getLowestPopulationEnergySeam(alignment=alignment, finishingBlocksToAvoid=finishingBlocksToAvoid) if lowestEnergySeamResult is None: return SplitType.NoSplit, None lowestEnergySeam = lowestEnergySeamResult[0] energySeamFinishingBlock = lowestEnergySeamResult[1] energySeamStartingEnergy = lowestEnergySeamResult[2] seamSplitPolygon = polygonFromMultipleGeometries(geometryList=lowestEnergySeam) polygonWithoutSeam = self.geometry.difference(seamSplitPolygon) # if the polygon without the seam is empty, that means we have a small enough redistricting group where # we need to break it up completely. Because our seams can no longer break up any further. if polygonWithoutSeam.is_empty: return SplitType.ForceSplitAllBlocks, None if type(polygonWithoutSeam) is MultiPolygon: seamOnEdge = False splitPolygons = list(polygonWithoutSeam) else: seamOnEdge = True splitPolygons = [polygonWithoutSeam, seamSplitPolygon] if alignment is Alignment.northSouth: aSplitRepresentativeBlockDirection = CardinalDirection.north bSplitRepresentativeBlockDirection = CardinalDirection.south else: aSplitRepresentativeBlockDirection = CardinalDirection.west bSplitRepresentativeBlockDirection = CardinalDirection.east # Identify which polygon is in which direction # Note: Need to make sure we don't select a block in the seam so we supply a list without those blocks # If the seam is completely on the edge though, let's include the seam if seamOnEdge: borderChildrenRepresentativeCandidates = self.borderChildren else: borderChildrenRepresentativeCandidates = [child for child in self.borderChildren if child not in lowestEnergySeam] if len(borderChildrenRepresentativeCandidates) == 0: return SplitType.NoSplit, None aSplitRepresentativeBlock = mostCardinalOfGeometries(geometryList=borderChildrenRepresentativeCandidates, direction=aSplitRepresentativeBlockDirection) bSplitRepresentativeBlock = mostCardinalOfGeometries(geometryList=borderChildrenRepresentativeCandidates, direction=bSplitRepresentativeBlockDirection) aSplitPolygon = getPolygonThatContainsGeometry(polygonList=splitPolygons, targetGeometry=aSplitRepresentativeBlock, useTargetRepresentativePoint=True) bSplitPolygon = getPolygonThatContainsGeometry(polygonList=splitPolygons, targetGeometry=bSplitRepresentativeBlock, useTargetRepresentativePoint=True) leftOverPolygons = [geometry for geometry in splitPolygons if geometry is not aSplitPolygon and geometry is not bSplitPolygon] if aSplitPolygon is None or bSplitPolygon is None: plotPolygons(splitPolygons + [aSplitRepresentativeBlock.geometry, bSplitRepresentativeBlock.geometry]) saveDataToFileWithDescription(data=self, censusYear='', stateName='', descriptionOfInfo='ErrorCase-AorBSplitIsNone') raise RuntimeError('Split a or b not found') if aSplitPolygon is bSplitPolygon: finishingBlocksToAvoid.append(energySeamFinishingBlock) continue if len(leftOverPolygons) is not len(splitPolygons) - 2: saveDataToFileWithDescription(data=self, censusYear='', stateName='', descriptionOfInfo='ErrorCase-MissingPolygons') raise RuntimeError('Missing some polygons for mapping. Split polygons: {0} Left over polygon: {1}' .format(len(splitPolygons), len(leftOverPolygons))) polygonSplits = (aSplitPolygon, bSplitPolygon) if shouldDrawGraph: plotPolygons(polygonSplits) if seamOnEdge: return SplitType.SplitIncludedInSeam, polygonSplits, None, energySeamStartingEnergy else: seamSplitPolygon = polygonFromMultiplePolygons(polygonList=[seamSplitPolygon] + leftOverPolygons) return SplitType.NormalSplit, polygonSplits, seamSplitPolygon, energySeamStartingEnergy
def updateBlockContainerData(self): self.geometry = polygonFromMultipleGeometries(self.children) self.population = censusBlock.populationFromBlocks(self.children)