首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

地图的四色原理着色实现:遗传算法+Python代码

  本文介绍利用Python语言,实现基于遗传算法GA)的地图四色原理着色操作。

1 任务需求

  首先,我们来明确一下本文所需实现的需求。

  现有一个由多个小图斑组成的矢量图层,如下图所示。

  我们需要找到一种由4种颜色组成的配色方案,对该矢量图层各图斑进行着色,使得各相邻小图斑间的颜色不一致,如下图所示。

  在这里,我们用到了四色定理(Four Color Theorem),又称四色地图定理(Four Color Map Theorem):如果在平面上存在一些邻接的有限区域,则至多仅用四种颜色来给这些不同的区域染色,就可以使得每两个邻接区域染的颜色都不一样。

2 代码实现

  明确了需求,我们就可以开始具体的代码编写。目前国内各大博客中,有很多关于Python实现地图四色原理着色的代码,其中大多数是基于回溯法来实现的;而在一个英文博客网页中,看到了基于遗传算法的地图四色原理着色实现。那么就以该代码为例,进行操作。在这里,由于我本人对于遗传算法的理解还并不深入,因此在代码介绍方面或多或少还存在着一定不足,希望大家多多批评指正。

2.1 基本思路

  遗传算法是一种用于解决最佳化问题的搜索算法,属于进化算法范畴。结合前述需求,首先可以将每一个区域的颜色作为一个基因,个体基因型则为全部地区(前述矢量图层共有78个小图斑,即78个区域)颜色基因的汇总;通过构建Rule类,将空间意义上的“相邻”转换为可以被遗传算法识别(即可以对个体基因改变加以约束)的信息;随后,结合子代的更替,找到满足要求的基因组;最终将得到的基因组再转换为空间意义上的颜色信息,并输出结果。

  具体分步骤思路如下:

定义“规则”。“规则”用以将区域之间的空间连接情况转换为遗传算法可以识别的信息;被“规则”连接的两个区域在空间中是相邻的。

定义区域空间连接情况检查所需函数。这些函数用于检查两两区域之间的连接性是否满足逻辑;例如,若在“规则”中显示区域A与区域B连接,那么区域B也必须在“规则”中显示与区域A连接。

定义个体基因型。其中,各个体具有78个基因,每一个基因表示一个区域的颜色。

个体更替与最优基因选择。通过个体的不断更迭,选择出满足“规则”要求的个体基因型。

基因型解释。将得到的个体基因型进行解释,相当于第一步的反过程,即将基因信息转换为空间连接情况。

结果检查。检查所得到的颜色与最优个体基因组中的各个基因是否一致。

2.2 代码讲解

  接下来,将完整代码进行介绍。其中,shapefile_path即为矢量图层的保存路径;"POLY_ID_OG"则为矢量图层的属性表中的一个字段,其代表每一个小图斑的编号。

1# -*- coding: utf-8 -*-

2"""

3Created on Sun Oct 31 19:22:33 2021

4

5@author: Chutj

6"""

7

8import genetic

9import unittest

10import datetime

11from libpysal.weights import Queen

12

13shapefile_path="G:/Python_Home1/stl_hom_utm.shp"

14

15weights=Queen.from_shapefile(shapefile_path,"POLY_ID_OG")

16one_neighbor_other=weights.neighbors

17

18# 定义“规则”,用以将区域之间的空间连接情况转换为遗传算法可以识别的信息。被“规则”连接的两个区域在空间中是相邻的

19

20class Rule:

21    Item = None

22    Other = None

23    Stringified = None

24

25    def __init__(self, item, other, stringified):

26        self.Item = item

27        self.Other = other

28        self.Stringified = stringified

29

30    def __eq__(self, another):

31        return hasattr(another, 'Item') and \

32               hasattr(another, 'Other') and \

33               self.Item == another.Item and \

34               self.Other == another.Other

35

36    def __hash__(self):

37        return hash(self.Item) * 397 ^ hash(self.Other)

38

39    def __str__(self):

40        return self.Stringified

41

42# 定义区域空间连接情况检查所需函数,用以确保区域两两之间相邻情况的准确

43

44def buildLookup(items):

45    itemToIndex = {}

46    index = 0

47    for key in sorted(items):

48        itemToIndex[key] = index

49        index += 1

50    return itemToIndex

51

52def buildRules(items):

53    itemToIndex = buildLookup(items.keys())

54    rulesAdded = {}

55    rules = []

56    keys = sorted(list(items.keys()))

57

58    for key in sorted(items.keys()):

59        keyIndex = itemToIndex[key]

60        adjacentKeys = items[key]

61        for adjacentKey in adjacentKeys:

62            if adjacentKey == '':

63                continue

64            adjacentIndex = itemToIndex[adjacentKey]

65            temp = keyIndex

66            if adjacentIndex 

67                temp, adjacentIndex = adjacentIndex, temp

68            ruleKey = str(keys[temp]) + "->" + str(keys[adjacentIndex])

69            rule = Rule(temp, adjacentIndex, ruleKey)

70            if rule in rulesAdded:

71                rulesAdded[rule] += 1

72            else:

73                rulesAdded[rule] = 1

74                rules.append(rule)

75

76    for k, v in rulesAdded.items():

77        if v == 1:

78            print("rule %s is not bidirectional" % k)

79

80    return rules

81

82# 定义颜色所代表的基因组

83

84colors = ["Orange", "Yellow", "Green", "Blue"]

85colorLookup = {}

86for color in colors:

87    colorLookup[color[0]] = color

88geneset = list(colorLookup.keys())

89

90# 定义个体基因型,其中各个体有78个基因,每一个基因代表一个区域。个体基因需要满足“规则”中相邻的区域具有不同的颜色

91

92class GraphColoringTests(unittest.TestCase):

93    def test(self):

94        rules = buildRules(one_neighbor_other)

95        colors = ["Orange", "Yellow", "Green", "Blue"]

96        colorLookup = {}

97        for color in colors:

98            colorLookup[color[0]] = color

99        geneset = list(colorLookup.keys())

100        optimalValue = len(rules)

101        startTime = datetime.datetime.now()

102        fnDisplay = lambda candidate: display(candidate, startTime)

103        fnGetFitness = lambda candidate: getFitness(candidate, rules)

104        best = genetic.getBest(fnGetFitness, fnDisplay, len(one_neighbor_other), optimalValue, geneset)

105        self.assertEqual(best.Fitness, optimalValue)

106

107        keys = sorted(one_neighbor_other.keys())

108

109        for index in range(len(one_neighbor_other)):

110            print(keys[index]," is ",colorLookup[best.Genes[index]])

111

112# 输出各区域颜色

113

114def display(candidate, startTime):

115    timeDiff = datetime.datetime.now() - startTime

116    print("%s\t%i\t%s" % (''.join(map(str, candidate.Genes)), candidate.Fitness, str(timeDiff)))

117

118# 检查各区域颜色是否与个体基因所代表的颜色一致

119

120def getFitness(candidate, rules):

121    rulesThatPass = 0

122    for rule in rules:

123        if candidate[rule.Item] != candidate[rule.Other]:

124            rulesThatPass += 1

125

126    return rulesThatPass

127

128# 运行程序

129

130GraphColoringTests().test()

2.3 结果展示

  执行上述代码,即可得到结果。在这里值得一提的是:这个代码不知道是其自身原因,还是我电脑的问题,执行起来非常慢——单次运行时间可能在5 ~ 6个小时左右,实在太慢了;大家如果感兴趣,可以尝试着能不能将代码的效率提升一下。

  代码执行完毕后得到的结果是文字形式的,具体如下图所示。

  可以看到,通过203次迭代,找到了满足要求的地图配色方案,用时06小时06分钟;代码执行结果除显示出具体个体的整体基因型之外,还将分别显示78个小区域(小图斑)各自的具体颜色名称(我上面那幅图没有截全,实际上是78个小区域的颜色都会输出的)。

  当然,大家也可以发现,这种文字表达的代码执行结果显然不如直接来一幅如下所示的结果图直观。但是,由于代码单次执行时间实在是太久了,我也没再腾出时间(其实是偷懒)对结果的可视化加以修改。大家如果感兴趣的话,可以尝试对代码最终的结果呈现部分加以修改——例如,可以通过Matplotlib库的拓展——Basemap库将78个小区域的配色方案进行可视化。

  至此,大功告成。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/Oy7HPWkKGES1NelsQ5A-Ibrg0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券