From f0b0ab21aa76f6a39195cf58da54ae86bc512bce Mon Sep 17 00:00:00 2001 From: Tom Pike Date: Mon, 12 Aug 2024 20:06:30 -0400 Subject: [PATCH] Update tutorial and viz - Update tutorial to be SIR model - Update Viz to allow for different markers; default agent to circle -Update tests --- .../data/TorontoNeighbourhoods.geojson | 16 + docs/tutorials/intro_tutorial.ipynb | 1898 ++++++++++++++++- mesa_geo/visualization/geojupyter_viz.py | 213 +- mesa_geo/visualization/leaflet_viz.py | 70 +- tests/test_GeoJupyterViz.py | 12 +- tests/test_MapModule.py | 28 +- 6 files changed, 1980 insertions(+), 257 deletions(-) create mode 100644 docs/tutorials/data/TorontoNeighbourhoods.geojson diff --git a/docs/tutorials/data/TorontoNeighbourhoods.geojson b/docs/tutorials/data/TorontoNeighbourhoods.geojson new file mode 100644 index 00000000..86372c68 --- /dev/null +++ b/docs/tutorials/data/TorontoNeighbourhoods.geojson @@ -0,0 +1,16 @@ +{ +"type": "FeatureCollection", +"name": "Toronto", +"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, +"features": [ +{ "type": "Feature", "properties": { "DAUID": "35202325", "PRUID": "35", "CSDUID": "3520005", "HOODNUM": 103, "HOOD": "Lawrence Park South", "FULLHOOD": "Lawrence Park South (103)" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -79.388979232123205, 43.721308240235665 ], [ -79.388380839335355, 43.719084895560265 ], [ -79.400414151289169, 43.716606883656979 ], [ -79.399785136452493, 43.71365383536812 ], [ -79.411443630677539, 43.711205418207157 ], [ -79.410371345426896, 43.708570333516498 ], [ -79.409628804285433, 43.705776710535716 ], [ -79.408831308223895, 43.705196244441318 ], [ -79.408453245228017, 43.704600007566697 ], [ -79.415941882637327, 43.703011377959236 ], [ -79.41707403747948, 43.707150739141944 ], [ -79.416112961118941, 43.707353564739442 ], [ -79.417607119695376, 43.710399611197914 ], [ -79.419477212009085, 43.710011211194569 ], [ -79.420729510434242, 43.71308114779189 ], [ -79.421980808359393, 43.716958974699047 ], [ -79.418553394317328, 43.717912807448563 ], [ -79.416928509014198, 43.717975118090983 ], [ -79.41550157335908, 43.718337751349502 ], [ -79.414519654720095, 43.718306675263293 ], [ -79.414566433540244, 43.718524359318849 ], [ -79.413731860292174, 43.718907245674309 ], [ -79.414297870931321, 43.722526740794237 ], [ -79.39021430303211, 43.72764188778973 ], [ -79.389682327756503, 43.725649034859309 ], [ -79.389603329162398, 43.724588312367089 ], [ -79.389250653387577, 43.723689098542472 ], [ -79.388979232123205, 43.721308240235665 ] ] ] } } +, +{ "type": "Feature", "properties": { "DAUID": "35202344", "PRUID": "35", "CSDUID": "3520005", "HOODNUM": 105, "HOOD": "Lawrence Park North", "FULLHOOD": "Lawrence Park North (105)" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -79.390882155425913, 43.729987722680477 ], [ -79.390358823303401, 43.72870713259811 ], [ -79.39021430303211, 43.72764188778973 ], [ -79.414297870931321, 43.722526740794237 ], [ -79.414681008399029, 43.724945096314627 ], [ -79.41568553405213, 43.727394836840794 ], [ -79.417027760781565, 43.733199737110688 ], [ -79.416349964450035, 43.733504965530749 ], [ -79.404880337349709, 43.73590303370672 ], [ -79.404576298630928, 43.734417896154561 ], [ -79.404184850850044, 43.734355721237094 ], [ -79.392820225983897, 43.736804633178963 ], [ -79.390494544890146, 43.730059232478482 ], [ -79.390882155425913, 43.729987722680477 ] ] ] } } +, +{ "type": "Feature", "properties": { "DAUID": "35200370", "PRUID": "35", "CSDUID": "3520005", "HOODNUM": 40, "HOOD": "St.Andrew-Windfields", "FULLHOOD": "St.Andrew-Windfields (40)" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -79.359952422301689, 43.752962799374949 ], [ -79.363196880408907, 43.752933952400944 ], [ -79.365497961172338, 43.75253298731878 ], [ -79.380650002918912, 43.749372301888187 ], [ -79.38257070950452, 43.748911862433872 ], [ -79.383546890659389, 43.748241259716103 ], [ -79.384150548937981, 43.748062285151335 ], [ -79.402014050256057, 43.744234544958964 ], [ -79.402833109260186, 43.744139588534502 ], [ -79.404086379463067, 43.744272763733228 ], [ -79.404331652757108, 43.743786847168067 ], [ -79.406521590880033, 43.743340113546608 ], [ -79.40808285799568, 43.750141688214953 ], [ -79.408068178986881, 43.752466744533621 ], [ -79.408326166010113, 43.753504105570059 ], [ -79.397855741898553, 43.760064744092737 ], [ -79.394524566662042, 43.761499979525055 ], [ -79.390428434237492, 43.76262350965461 ], [ -79.376582688623657, 43.765387975458253 ], [ -79.371750460231326, 43.765846398646666 ], [ -79.359648969676826, 43.766414966764366 ], [ -79.359468999049071, 43.765731038733712 ], [ -79.357844970875504, 43.764858042841581 ], [ -79.357536982066691, 43.764728043328745 ], [ -79.356124028332303, 43.764814045616617 ], [ -79.354928991462003, 43.764618979881867 ], [ -79.354177036164401, 43.763668999757357 ], [ -79.353539050614643, 43.763614984248001 ], [ -79.352658971599013, 43.76383797276771 ], [ -79.351974980780341, 43.763534025940722 ], [ -79.351359050202504, 43.76354999007215 ], [ -79.351141011043737, 43.763972990326515 ], [ -79.350742005209383, 43.764113963917836 ], [ -79.349585011072847, 43.763572011945783 ], [ -79.349254027394792, 43.763165033035655 ], [ -79.349606986650699, 43.762362038086927 ], [ -79.349810000713902, 43.761042965082325 ], [ -79.34896896600948, 43.760696045060449 ], [ -79.348683006090937, 43.760190964845584 ], [ -79.348285031538154, 43.759930014616515 ], [ -79.346503038317792, 43.759534010413809 ], [ -79.346030049794081, 43.758589991060049 ], [ -79.344600996136975, 43.757710985296946 ], [ -79.34383504335355, 43.756489959471843 ], [ -79.359952422301689, 43.752962799374949 ] ] ] } } +, +{ "type": "Feature", "properties": { "DAUID": "35202608", "PRUID": "35", "CSDUID": "3520005", "HOODNUM": 41, "HOOD": "Bridle Path-Sunnybrook-York Mills", "FULLHOOD": "Bridle Path-Sunnybrook-York Mills (41)" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -79.361585608453339, 43.719794166619586 ], [ -79.365769670599292, 43.719268029451932 ], [ -79.378066348250684, 43.716724032604894 ], [ -79.377430659846183, 43.713140546205558 ], [ -79.381863865668834, 43.712017108032072 ], [ -79.385911596028663, 43.711341751088881 ], [ -79.388979232123205, 43.721308240235665 ], [ -79.389250653387577, 43.723689098542472 ], [ -79.389603329162398, 43.724588312367089 ], [ -79.390358823303401, 43.72870713259811 ], [ -79.390882155425913, 43.729987722680477 ], [ -79.390494544890146, 43.730059232478482 ], [ -79.392820225983897, 43.736804633178963 ], [ -79.404184850850044, 43.734355721237094 ], [ -79.404576298630928, 43.734417896154561 ], [ -79.406521590880033, 43.743340113546608 ], [ -79.404331652757108, 43.743786847168067 ], [ -79.404086379463067, 43.744272763733228 ], [ -79.402833109260186, 43.744139588534502 ], [ -79.402014050256057, 43.744234544958964 ], [ -79.384150548937981, 43.748062285151335 ], [ -79.383546890659389, 43.748241259716103 ], [ -79.382661286359394, 43.748849590924543 ], [ -79.382458494459101, 43.748632658332888 ], [ -79.381315718557502, 43.748175304131657 ], [ -79.38116282820458, 43.747398801712571 ], [ -79.380260350184912, 43.747241899743379 ], [ -79.379921264116319, 43.746922030960896 ], [ -79.379915325294789, 43.746678807284987 ], [ -79.379124160622382, 43.746082383829162 ], [ -79.378773679530312, 43.746185436250371 ], [ -79.378379513906211, 43.746062732098665 ], [ -79.37852409015818, 43.745308538589974 ], [ -79.378811070972702, 43.745258597477168 ], [ -79.378807180602294, 43.744943388044959 ], [ -79.37836142302389, 43.744432873244186 ], [ -79.378158583329679, 43.74366465971017 ], [ -79.377940551169004, 43.743463436004362 ], [ -79.376020424829221, 43.742364778095542 ], [ -79.375389943425859, 43.742256710178403 ], [ -79.374897018213673, 43.741655493011926 ], [ -79.37440067041473, 43.741639434644441 ], [ -79.373285887221044, 43.741074375754444 ], [ -79.37301670522784, 43.740467241915901 ], [ -79.372291887524057, 43.740636961434419 ], [ -79.372021940332075, 43.740516093803897 ], [ -79.371987315337194, 43.740875764394538 ], [ -79.371761410188725, 43.740962565643493 ], [ -79.371667475923545, 43.740763187285545 ], [ -79.371382705303603, 43.740732125423953 ], [ -79.371328961979074, 43.740425207203387 ], [ -79.370948993437594, 43.740698850626003 ], [ -79.370625986420222, 43.740703249393043 ], [ -79.370625206800213, 43.740271059900948 ], [ -79.370998834456202, 43.740231375063942 ], [ -79.371144899507101, 43.739882384404154 ], [ -79.371025223640075, 43.739718556079922 ], [ -79.370677590998639, 43.739713602275017 ], [ -79.370597999428668, 43.739442412670634 ], [ -79.371045655866567, 43.739421786412272 ], [ -79.371076384443796, 43.739206089998333 ], [ -79.370419463811302, 43.739160720844097 ], [ -79.370199721281764, 43.738563286759707 ], [ -79.369770801033468, 43.738350128947928 ], [ -79.369543682254445, 43.738481917766393 ], [ -79.369604902309717, 43.738059614158018 ], [ -79.369235680803214, 43.737937324118349 ], [ -79.368918971408277, 43.738166854012114 ], [ -79.368573301524194, 43.738089907063092 ], [ -79.368062230746546, 43.737704445448252 ], [ -79.368008748505758, 43.737388617438242 ], [ -79.368198120944655, 43.737274206560961 ], [ -79.367881483467343, 43.737044640528381 ], [ -79.367426591978685, 43.736876022323308 ], [ -79.367210879561014, 43.737044066817653 ], [ -79.366751690055011, 43.737037507777586 ], [ -79.366642603799747, 43.736936839006901 ], [ -79.366824233407087, 43.736651374729597 ], [ -79.365980907623651, 43.736612318902004 ], [ -79.365489896397349, 43.736857351642456 ], [ -79.364420280429641, 43.736463886805566 ], [ -79.364335437164371, 43.735931474041863 ], [ -79.363770497450574, 43.736157528016392 ], [ -79.363398363213591, 43.736143109543377 ], [ -79.363218541456831, 43.735906487200943 ], [ -79.363640182331864, 43.735471342498592 ], [ -79.363263022662139, 43.73472761199691 ], [ -79.362116738475834, 43.73441404450697 ], [ -79.360880385183918, 43.733306921463814 ], [ -79.36010257598295, 43.733142645671364 ], [ -79.3591938415275, 43.733228627418171 ], [ -79.359084176713679, 43.732695854293119 ], [ -79.359376162787072, 43.732006722256081 ], [ -79.358817036188711, 43.731566518283394 ], [ -79.357682235327815, 43.731289168501654 ], [ -79.357551211760622, 43.731089155473782 ], [ -79.357694873201609, 43.730830165560384 ], [ -79.35702606394625, 43.730766454927732 ], [ -79.357207382327204, 43.7300398203827 ], [ -79.356975262357309, 43.729901366897643 ], [ -79.356168454175702, 43.729889769063924 ], [ -79.35587603418621, 43.729687523347074 ], [ -79.354646266260147, 43.729705838792022 ], [ -79.35446280467761, 43.729604177908158 ], [ -79.354405709208137, 43.729423228996957 ], [ -79.354671556760664, 43.729238016334619 ], [ -79.354089098059362, 43.728743442174718 ], [ -79.353967596970676, 43.728192399680538 ], [ -79.353586085194976, 43.728069882014587 ], [ -79.353344881661528, 43.727355081477917 ], [ -79.352895526779747, 43.726988444158088 ], [ -79.35271571493567, 43.726301622234594 ], [ -79.352169374056871, 43.726302750749873 ], [ -79.351670254588001, 43.725944393134888 ], [ -79.350739362286006, 43.72593096792221 ], [ -79.350070924075553, 43.725408127139758 ], [ -79.350291927537327, 43.724591967122038 ], [ -79.350748362432583, 43.724247389905941 ], [ -79.349941100934998, 43.723803562272487 ], [ -79.349898875389343, 43.72353280695814 ], [ -79.350135399557544, 43.723509214943142 ], [ -79.350176598997393, 43.723365779699392 ], [ -79.349867651633701, 43.723316311604023 ], [ -79.349870863156283, 43.723199243729169 ], [ -79.350247604246405, 43.723042647275378 ], [ -79.350282490209082, 43.72267398428167 ], [ -79.351174601891046, 43.722740865050348 ], [ -79.351465440683484, 43.72254701818332 ], [ -79.351911833636279, 43.722112272730662 ], [ -79.351933935583332, 43.721761428602576 ], [ -79.352484917586594, 43.721589332604289 ], [ -79.352723737894834, 43.721025476318651 ], [ -79.353047624255211, 43.720985133239701 ], [ -79.35353454671197, 43.720433940878912 ], [ -79.353668427484735, 43.721059428174065 ], [ -79.361585608453339, 43.719794166619586 ] ] ] } } +, +{ "type": "Feature", "properties": { "DAUID": "35200376", "PRUID": "35", "CSDUID": "3520005", "HOODNUM": 42, "HOOD": "Banbury-Don Mills", "FULLHOOD": "Banbury-Don Mills (42)" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -79.330149781248252, 43.723153876628579 ], [ -79.352670283743592, 43.716210776284626 ], [ -79.353094553700217, 43.716555989299103 ], [ -79.353606181865388, 43.72041615859866 ], [ -79.353047624255211, 43.720985133239701 ], [ -79.352723737894834, 43.721025476318651 ], [ -79.352484917586594, 43.721589332604289 ], [ -79.351933935583332, 43.721761428602576 ], [ -79.351911833636279, 43.722112272730662 ], [ -79.351465440683484, 43.72254701818332 ], [ -79.351174601891046, 43.722740865050348 ], [ -79.350282490209082, 43.72267398428167 ], [ -79.350247604246405, 43.723042647275378 ], [ -79.349870863156283, 43.723199243729169 ], [ -79.349867651633701, 43.723316311604023 ], [ -79.350176598997393, 43.723365779699392 ], [ -79.350135399557544, 43.723509214943142 ], [ -79.349898875389343, 43.72353280695814 ], [ -79.349941100934998, 43.723803562272487 ], [ -79.350748362432583, 43.724247389905941 ], [ -79.350291927537327, 43.724591967122038 ], [ -79.350067923274835, 43.725065923156066 ], [ -79.350155095549056, 43.725508362304296 ], [ -79.350739362286006, 43.72593096792221 ], [ -79.351670254588001, 43.725944393134888 ], [ -79.352169374056871, 43.726302750749873 ], [ -79.35271571493567, 43.726301622234594 ], [ -79.352895526779747, 43.726988444158088 ], [ -79.353344881661528, 43.727355081477917 ], [ -79.353586085194976, 43.728069882014587 ], [ -79.353967596970676, 43.728192399680538 ], [ -79.354089098059362, 43.728743442174718 ], [ -79.354671556760664, 43.729238016334619 ], [ -79.354405709208137, 43.729423228996957 ], [ -79.35446280467761, 43.729604177908158 ], [ -79.354646266260147, 43.729705838792022 ], [ -79.35587603418621, 43.729687523347074 ], [ -79.356168454175702, 43.729889769063924 ], [ -79.356975262357309, 43.729901366897643 ], [ -79.357207382327204, 43.7300398203827 ], [ -79.35702606394625, 43.730766454927732 ], [ -79.357694873201609, 43.730830165560384 ], [ -79.357551211760622, 43.731089155473782 ], [ -79.357682235327815, 43.731289168501654 ], [ -79.358817036188711, 43.731566518283394 ], [ -79.359376162787072, 43.732006722256081 ], [ -79.359084176713679, 43.732695854293119 ], [ -79.3591938415275, 43.733228627418171 ], [ -79.36010257598295, 43.733142645671364 ], [ -79.360880385183918, 43.733306921463814 ], [ -79.362116738475834, 43.73441404450697 ], [ -79.363263022662139, 43.73472761199691 ], [ -79.363640182331864, 43.735471342498592 ], [ -79.363218541456831, 43.735906487200943 ], [ -79.363398363213591, 43.736143109543377 ], [ -79.363770497450574, 43.736157528016392 ], [ -79.364335437164371, 43.735931474041863 ], [ -79.364420280429641, 43.736463886805566 ], [ -79.365489896397349, 43.736857351642456 ], [ -79.365980907623651, 43.736612318902004 ], [ -79.366824233407087, 43.736651374729597 ], [ -79.366642603799747, 43.736936839006901 ], [ -79.366751690055011, 43.737037507777586 ], [ -79.367210879561014, 43.737044066817653 ], [ -79.367426591978685, 43.736876022323308 ], [ -79.367881483467343, 43.737044640528381 ], [ -79.368198120944655, 43.737274206560961 ], [ -79.368008748505758, 43.737388617438242 ], [ -79.368062230746546, 43.737704445448252 ], [ -79.368573301524194, 43.738089907063092 ], [ -79.368918971408277, 43.738166854012114 ], [ -79.369235680803214, 43.737937324118349 ], [ -79.369604902309717, 43.738059614158018 ], [ -79.369543682254445, 43.738481917766393 ], [ -79.369770801033468, 43.738350128947928 ], [ -79.370199721281764, 43.738563286759707 ], [ -79.370419463811302, 43.739160720844097 ], [ -79.371076384443796, 43.739206089998333 ], [ -79.371045655866567, 43.739421786412272 ], [ -79.370597999428668, 43.739442412670634 ], [ -79.370677590998639, 43.739713602275017 ], [ -79.371025223640075, 43.739718556079922 ], [ -79.371144899507101, 43.739882384404154 ], [ -79.370998834456202, 43.740231375063942 ], [ -79.370625206800213, 43.740271059900948 ], [ -79.370625986420222, 43.740703249393043 ], [ -79.370948993437594, 43.740698850626003 ], [ -79.371328961979074, 43.740425207203387 ], [ -79.371382705303603, 43.740732125423953 ], [ -79.371667475923545, 43.740763187285545 ], [ -79.371761410188725, 43.740962565643493 ], [ -79.371987315337194, 43.740875764394538 ], [ -79.372021940332075, 43.740516093803897 ], [ -79.372291887524057, 43.740636961434419 ], [ -79.37301670522784, 43.740467241915901 ], [ -79.373285887221044, 43.741074375754444 ], [ -79.37440067041473, 43.741639434644441 ], [ -79.374897018213673, 43.741655493011926 ], [ -79.375389943425859, 43.742256710178403 ], [ -79.376020424829221, 43.742364778095542 ], [ -79.377940551169004, 43.743463436004362 ], [ -79.378158583329679, 43.74366465971017 ], [ -79.37836142302389, 43.744432873244186 ], [ -79.378807180602294, 43.744943388044959 ], [ -79.378811070972702, 43.745258597477168 ], [ -79.37852409015818, 43.745308538589974 ], [ -79.378379513906211, 43.746062732098665 ], [ -79.378773679530312, 43.746185436250371 ], [ -79.379124160622382, 43.746082383829162 ], [ -79.379915325294789, 43.746678807284987 ], [ -79.379921264116319, 43.746922030960896 ], [ -79.380260350184912, 43.747241899743379 ], [ -79.38116282820458, 43.747398801712571 ], [ -79.381315718557502, 43.748175304131657 ], [ -79.382458494459101, 43.748632658332888 ], [ -79.382661286359394, 43.748849590924543 ], [ -79.365497961172338, 43.75253298731878 ], [ -79.363196880408907, 43.752933952400944 ], [ -79.359952422301689, 43.752962799374949 ], [ -79.34383504335355, 43.756489959471843 ], [ -79.343608975835579, 43.755181962950978 ], [ -79.34382003884491, 43.755009035569024 ], [ -79.344833969245428, 43.754802005511678 ], [ -79.345570971425573, 43.754302991399008 ], [ -79.345571046958966, 43.753678987322608 ], [ -79.345270024488642, 43.752848984906798 ], [ -79.346389997434628, 43.751518969813468 ], [ -79.345247970896438, 43.751008966062741 ], [ -79.343098033387122, 43.750932997578929 ], [ -79.342541966797896, 43.750780998044867 ], [ -79.342572032434717, 43.749832012730195 ], [ -79.34217403375861, 43.74898502157977 ], [ -79.342392041438558, 43.748166046223368 ], [ -79.342248990800741, 43.747634034558644 ], [ -79.340534963186755, 43.746961035685281 ], [ -79.33933195892871, 43.746727973185948 ], [ -79.339144050571932, 43.746538005453289 ], [ -79.339790990095125, 43.745675035229937 ], [ -79.340167042152132, 43.744856018266866 ], [ -79.340129001982248, 43.744535000848352 ], [ -79.339211957061622, 43.743569018763878 ], [ -79.338039952639079, 43.743498966013235 ], [ -79.337107011812222, 43.743846012907618 ], [ -79.336445985179751, 43.743492976127307 ], [ -79.336206044584983, 43.742896983916999 ], [ -79.336596035912692, 43.7424080083672 ], [ -79.336565972488302, 43.742077023670028 ], [ -79.335603958615778, 43.740855966208485 ], [ -79.334883021169532, 43.740681994682305 ], [ -79.333521991146014, 43.740801961561367 ], [ -79.333123985518327, 43.740959002310888 ], [ -79.332785954744153, 43.741372028258816 ], [ -79.332177051143844, 43.741534001504498 ], [ -79.331168986549287, 43.741539994845702 ], [ -79.330605997915598, 43.741269011700389 ], [ -79.33047795735159, 43.740823981699542 ], [ -79.330839019894796, 43.740172030100275 ], [ -79.330154963813683, 43.739272029152069 ], [ -79.330079967689059, 43.738783034223907 ], [ -79.330312965168659, 43.738251008183987 ], [ -79.331559991405172, 43.737181972291225 ], [ -79.331402024856061, 43.736807980887356 ], [ -79.330672968030456, 43.736368039929403 ], [ -79.331184005120505, 43.735483958623178 ], [ -79.331064045005732, 43.734843976916991 ], [ -79.329974015331885, 43.734099990052414 ], [ -79.32949298641941, 43.733948032541583 ], [ -79.329117032066449, 43.733953970769406 ], [ -79.328583977457427, 43.734285008051614 ], [ -79.328312967011115, 43.734236036321555 ], [ -79.327795004734242, 43.733817977253324 ], [ -79.327591967761393, 43.733015013453894 ], [ -79.326937978456371, 43.73287896862734 ], [ -79.326057977599874, 43.732174025104726 ], [ -79.324772964427225, 43.731685036139758 ], [ -79.32459304599351, 43.731105021208052 ], [ -79.325261968086792, 43.729715035068942 ], [ -79.325103989653769, 43.729519991148557 ], [ -79.323104025240411, 43.729444043637955 ], [ -79.320218045809654, 43.731565989506535 ], [ -79.319744041746731, 43.731592960794821 ], [ -79.319609036287616, 43.731430023502384 ], [ -79.319608979321814, 43.730996043726982 ], [ -79.320119985074712, 43.730524025348174 ], [ -79.320376039524575, 43.729644976462886 ], [ -79.320112988249733, 43.729477007767066 ], [ -79.318993025842772, 43.729324977532301 ], [ -79.318737033848009, 43.72920499929382 ], [ -79.318707033081367, 43.728945034821223 ], [ -79.320120045989015, 43.728461973054351 ], [ -79.320457993959238, 43.72800100267191 ], [ -79.323149003094542, 43.727170025951686 ], [ -79.323127037476695, 43.726231980835465 ], [ -79.322390018403084, 43.725618043909961 ], [ -79.322299951367427, 43.725205967626373 ], [ -79.330149781248252, 43.723153876628579 ] ] ] } } +] +} diff --git a/docs/tutorials/intro_tutorial.ipynb b/docs/tutorials/intro_tutorial.ipynb index 21362d52..af497418 100644 --- a/docs/tutorials/intro_tutorial.ipynb +++ b/docs/tutorials/intro_tutorial.ipynb @@ -1,224 +1,1917 @@ { "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "938afa81-e56a-45c8-9790-70a3f1739977", + "metadata": { + "has_explanation": false, + "jupyter": { + "source_hidden": true + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['C:\\\\Users\\\\thoma\\\\miniconda3\\\\envs\\\\mesa_geo\\\\python312.zip', 'C:\\\\Users\\\\thoma\\\\miniconda3\\\\envs\\\\mesa_geo\\\\DLLs', 'C:\\\\Users\\\\thoma\\\\miniconda3\\\\envs\\\\mesa_geo\\\\Lib', 'C:\\\\Users\\\\thoma\\\\miniconda3\\\\envs\\\\mesa_geo', '', 'C:\\\\Users\\\\thoma\\\\miniconda3\\\\envs\\\\mesa_geo\\\\Lib\\\\site-packages', 'C:\\\\Users\\\\thoma\\\\miniconda3\\\\envs\\\\mesa_geo\\\\Lib\\\\site-packages\\\\win32', 'C:\\\\Users\\\\thoma\\\\miniconda3\\\\envs\\\\mesa_geo\\\\Lib\\\\site-packages\\\\win32\\\\lib', 'C:\\\\Users\\\\thoma\\\\miniconda3\\\\envs\\\\mesa_geo\\\\Lib\\\\site-packages\\\\Pythonwin', 'C:\\\\Users\\\\thoma\\\\Documents\\\\GitHub\\\\dev\\\\mesa-geo']\n" + ] + } + ], + "source": [ + "import os\n", + "import sys\n", + "\n", + "# Add the directory where the local file is located to the system path\n", + "sys.path.append(r\"C:\\Users\\thoma\\Documents\\GitHub\\dev\\mesa-geo\")\n", + "\n", + "print(sys.path) # To check if the directory is added\n", + "\n" + ] + }, { "cell_type": "markdown", - "id": "670cf30d", + "id": "4d52735e-0198-403b-bd9d-56cb12d4dde2", + "metadata": {}, + "source": [ + "# Mesa-Geo Introductory Model \n", + "\n", + "To overview the critical parts of Mesa-Geo this tutorial uses the pandemic modelling approach known as a S(usceptible), I(infected) and R(ecovered) or SIR model. \n", + "\n", + "Components of the model are:\n", + "\n", + "**Agents:** Each agent in the model represents an individual in the population. Agents have states of susceptible, infected, recovered, or dead. The Agents are point agents, randomly placed into the envirnoment.\n", + "\n", + "**Environment:** The envirnment is a set of polygons of a few Toronto neighborhoods. \n", + "\n", + "**Interaction Rules:** Susceptible agents can become infected with a certain probability, if they come into contact with infected agents. Infected agents then recover after a certain period or perish based on a probability.\n", + "\n", + "**Parameters:** \n", + "- Population Size (number of human agents in the model)\n", + "- Initial Infection (percent of the population initial infected)\n", + "- Exposure Distance (proximity suscpetible agents must be to infected agents to possibly get infected)\n", + "- Infection Risk (probability of becoming infected)\n", + "- Recovery Rate (time infection lasts)\n", + "- Mobility (distance agent moves)\n", + "\n", + "### The tutorial then proceeds in three parts: \n", + "- Part 1 Create the Basic Model \n", + "- Part 2 Add Agent Behaviors and Model Complexity \n", + "- Part 3 Add Visualizations and Interface\n", + "\n", + " (You can use the table of contents button on the left side of the interface to skip to any specific part)\n", + "\n", + "\n", + "Users can use Google Colab (Please ensure you run the Colab dependency import cell) \n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/projectmesa/mesa-geo/blob/main/docs/tutorials/intro_tutorial.ipynb)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c0c702b3-c307-4f00-9530-468e29f53184", "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: mesa in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (2.3.0)\n", + "Requirement already satisfied: click in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa) (8.1.7)\n", + "Requirement already satisfied: cookiecutter in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa) (2.6.0)\n", + "Requirement already satisfied: matplotlib in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa) (3.9.0)\n", + "Requirement already satisfied: mesa-viz-tornado>=0.1.3,~=0.1.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa) (0.1.3)\n", + "Requirement already satisfied: networkx in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa) (3.3)\n", + "Requirement already satisfied: numpy in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa) (1.26.4)\n", + "Requirement already satisfied: pandas in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa) (2.2.2)\n", + "Requirement already satisfied: solara in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa) (1.37.1)\n", + "Requirement already satisfied: tqdm in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa) (4.66.4)\n", + "Requirement already satisfied: tornado in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa-viz-tornado>=0.1.3,~=0.1.0->mesa) (6.4)\n", + "Requirement already satisfied: colorama in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from click->mesa) (0.4.6)\n", + "Requirement already satisfied: binaryornot>=0.4.4 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from cookiecutter->mesa) (0.4.4)\n", + "Requirement already satisfied: Jinja2<4.0.0,>=2.7 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from cookiecutter->mesa) (3.1.4)\n", + "Requirement already satisfied: pyyaml>=5.3.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from cookiecutter->mesa) (6.0.1)\n", + "Requirement already satisfied: python-slugify>=4.0.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from cookiecutter->mesa) (8.0.4)\n", + "Requirement already satisfied: requests>=2.23.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from cookiecutter->mesa) (2.32.3)\n", + "Requirement already satisfied: arrow in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from cookiecutter->mesa) (1.3.0)\n", + "Requirement already satisfied: rich in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from cookiecutter->mesa) (13.7.1)\n", + "Requirement already satisfied: contourpy>=1.0.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from matplotlib->mesa) (1.2.1)\n", + "Requirement already satisfied: cycler>=0.10 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from matplotlib->mesa) (0.12.1)\n", + "Requirement already satisfied: fonttools>=4.22.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from matplotlib->mesa) (4.53.0)\n", + "Requirement already satisfied: kiwisolver>=1.3.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from matplotlib->mesa) (1.4.5)\n", + "Requirement already satisfied: packaging>=20.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from matplotlib->mesa) (24.0)\n", + "Requirement already satisfied: pillow>=8 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from matplotlib->mesa) (10.3.0)\n", + "Requirement already satisfied: pyparsing>=2.3.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from matplotlib->mesa) (3.1.2)\n", + "Requirement already satisfied: python-dateutil>=2.7 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from matplotlib->mesa) (2.9.0)\n", + "Requirement already satisfied: pytz>=2020.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from pandas->mesa) (2024.1)\n", + "Requirement already satisfied: tzdata>=2022.7 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from pandas->mesa) (2024.1)\n", + "Requirement already satisfied: solara-server==1.37.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-server[dev,starlette]==1.37.1->solara->mesa) (1.37.1)\n", + "Requirement already satisfied: solara-ui==1.37.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-ui[all]==1.37.1->solara->mesa) (1.37.1)\n", + "Requirement already satisfied: filelock in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa) (3.14.0)\n", + "Requirement already satisfied: ipykernel in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa) (6.29.3)\n", + "Requirement already satisfied: jupyter-client>=7.0.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa) (8.6.2)\n", + "Requirement already satisfied: nbformat in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa) (5.10.4)\n", + "Requirement already satisfied: rich-click in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa) (1.8.2)\n", + "Requirement already satisfied: watchdog in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-server[dev,starlette]==1.37.1->solara->mesa) (4.0.1)\n", + "Requirement already satisfied: watchfiles in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-server[dev,starlette]==1.37.1->solara->mesa) (0.22.0)\n", + "Requirement already satisfied: starlette in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-server[dev,starlette]==1.37.1->solara->mesa) (0.37.2)\n", + "Requirement already satisfied: uvicorn in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-server[dev,starlette]==1.37.1->solara->mesa) (0.30.1)\n", + "Requirement already satisfied: websockets in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-server[dev,starlette]==1.37.1->solara->mesa) (12.0)\n", + "Requirement already satisfied: humanize in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa) (4.9.0)\n", + "Requirement already satisfied: ipyvue>=1.9.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa) (1.11.1)\n", + "Requirement already satisfied: ipyvuetify>=1.6.10 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa) (1.9.4)\n", + "Requirement already satisfied: ipywidgets>=7.7 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa) (8.1.3)\n", + "Requirement already satisfied: reacton>=1.7.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa) (1.8.3)\n", + "Requirement already satisfied: chardet>=3.0.2 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from binaryornot>=0.4.4->cookiecutter->mesa) (5.2.0)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from Jinja2<4.0.0,>=2.7->cookiecutter->mesa) (2.1.5)\n", + "Requirement already satisfied: six>=1.5 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from python-dateutil>=2.7->matplotlib->mesa) (1.16.0)\n", + "Requirement already satisfied: text-unidecode>=1.3 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from python-slugify>=4.0.0->cookiecutter->mesa) (1.3)\n", + "Requirement already satisfied: charset-normalizer<4,>=2 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from requests>=2.23.0->cookiecutter->mesa) (3.3.2)\n", + "Requirement already satisfied: idna<4,>=2.5 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from requests>=2.23.0->cookiecutter->mesa) (3.7)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from requests>=2.23.0->cookiecutter->mesa) (2.2.1)\n", + "Requirement already satisfied: certifi>=2017.4.17 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from requests>=2.23.0->cookiecutter->mesa) (2024.2.2)\n", + "Requirement already satisfied: types-python-dateutil>=2.8.10 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from arrow->cookiecutter->mesa) (2.9.0.20240316)\n", + "Requirement already satisfied: markdown-it-py>=2.2.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from rich->cookiecutter->mesa) (3.0.0)\n", + "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from rich->cookiecutter->mesa) (2.18.0)\n", + "Requirement already satisfied: comm>=0.1.3 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipywidgets>=7.7->solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa) (0.2.2)\n", + "Requirement already satisfied: ipython>=6.1.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipywidgets>=7.7->solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa) (8.25.0)\n", + "Requirement already satisfied: traitlets>=4.3.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipywidgets>=7.7->solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa) (5.14.3)\n", + "Requirement already satisfied: widgetsnbextension~=4.0.11 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipywidgets>=7.7->solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa) (4.0.11)\n", + "Requirement already satisfied: jupyterlab-widgets~=3.0.11 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipywidgets>=7.7->solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa) (3.0.11)\n", + "Requirement already satisfied: jupyter-core!=5.0.*,>=4.12 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from jupyter-client>=7.0.0->solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa) (5.7.2)\n", + "Requirement already satisfied: pyzmq>=23.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from jupyter-client>=7.0.0->solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa) (26.0.3)\n", + "Requirement already satisfied: mdurl~=0.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from markdown-it-py>=2.2.0->rich->cookiecutter->mesa) (0.1.2)\n", + "Requirement already satisfied: typing-extensions>=4.1.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from reacton>=1.7.1->solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa) (4.12.1)\n", + "Requirement already satisfied: debugpy>=1.6.5 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipykernel->solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa) (1.8.1)\n", + "Requirement already satisfied: matplotlib-inline>=0.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipykernel->solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa) (0.1.7)\n", + "Requirement already satisfied: nest-asyncio in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipykernel->solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa) (1.6.0)\n", + "Requirement already satisfied: psutil in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipykernel->solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa) (5.9.8)\n", + "Requirement already satisfied: fastjsonschema>=2.15 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from nbformat->solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa) (2.19.1)\n", + "Requirement already satisfied: jsonschema>=2.6 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from nbformat->solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa) (4.22.0)\n", + "Requirement already satisfied: cachetools in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa) (5.3.3)\n", + "Requirement already satisfied: markdown in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa) (3.6)\n", + "Requirement already satisfied: pymdown-extensions in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa) (10.8.1)\n", + "Requirement already satisfied: anyio<5,>=3.4.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from starlette->solara-server[dev,starlette]==1.37.1->solara->mesa) (4.3.0)\n", + "Requirement already satisfied: h11>=0.8 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from uvicorn->solara-server[dev,starlette]==1.37.1->solara->mesa) (0.14.0)\n", + "Requirement already satisfied: sniffio>=1.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from anyio<5,>=3.4.0->starlette->solara-server[dev,starlette]==1.37.1->solara->mesa) (1.3.1)\n", + "Requirement already satisfied: decorator in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipython>=6.1.0->ipywidgets>=7.7->solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa) (5.1.1)\n", + "Requirement already satisfied: jedi>=0.16 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipython>=6.1.0->ipywidgets>=7.7->solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa) (0.19.1)\n", + "Requirement already satisfied: prompt-toolkit<3.1.0,>=3.0.41 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipython>=6.1.0->ipywidgets>=7.7->solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa) (3.0.46)\n", + "Requirement already satisfied: stack-data in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipython>=6.1.0->ipywidgets>=7.7->solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa) (0.6.2)\n", + "Requirement already satisfied: attrs>=22.2.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from jsonschema>=2.6->nbformat->solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa) (23.2.0)\n", + "Requirement already satisfied: jsonschema-specifications>=2023.03.6 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from jsonschema>=2.6->nbformat->solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa) (2023.12.1)\n", + "Requirement already satisfied: referencing>=0.28.4 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from jsonschema>=2.6->nbformat->solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa) (0.35.1)\n", + "Requirement already satisfied: rpds-py>=0.7.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from jsonschema>=2.6->nbformat->solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa) (0.18.1)\n", + "Requirement already satisfied: platformdirs>=2.5 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from jupyter-core!=5.0.*,>=4.12->jupyter-client>=7.0.0->solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa) (4.2.2)\n", + "Requirement already satisfied: pywin32>=300 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from jupyter-core!=5.0.*,>=4.12->jupyter-client>=7.0.0->solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa) (306)\n", + "Requirement already satisfied: parso<0.9.0,>=0.8.3 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from jedi>=0.16->ipython>=6.1.0->ipywidgets>=7.7->solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa) (0.8.4)\n", + "Requirement already satisfied: wcwidth in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from prompt-toolkit<3.1.0,>=3.0.41->ipython>=6.1.0->ipywidgets>=7.7->solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa) (0.2.13)\n", + "Requirement already satisfied: executing>=1.2.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from stack-data->ipython>=6.1.0->ipywidgets>=7.7->solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa) (2.0.1)\n", + "Requirement already satisfied: asttokens>=2.1.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from stack-data->ipython>=6.1.0->ipywidgets>=7.7->solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa) (2.4.1)\n", + "Requirement already satisfied: pure-eval in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from stack-data->ipython>=6.1.0->ipywidgets>=7.7->solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa) (0.2.2)\n", + "Requirement already satisfied: mesa-geo in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (0.7.1)\n", + "Requirement already satisfied: folium in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa-geo) (0.16.0)\n", + "Requirement already satisfied: geopandas in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa-geo) (0.14.4)\n", + "Requirement already satisfied: ipyleaflet in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa-geo) (0.19.1)\n", + "Requirement already satisfied: libpysal in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa-geo) (4.10)\n", + "Requirement already satisfied: mesa in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa-geo) (2.3.0)\n", + "Requirement already satisfied: pyproj in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa-geo) (3.6.1)\n", + "Requirement already satisfied: rasterio in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa-geo) (1.3.10)\n", + "Requirement already satisfied: rtree in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa-geo) (1.2.0)\n", + "Requirement already satisfied: shapely in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa-geo) (2.0.4)\n", + "Requirement already satisfied: xyzservices>=2022.9.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa-geo) (2024.4.0)\n", + "Requirement already satisfied: branca>=0.6.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from folium->mesa-geo) (0.7.2)\n", + "Requirement already satisfied: jinja2>=2.9 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from folium->mesa-geo) (3.1.4)\n", + "Requirement already satisfied: numpy in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from folium->mesa-geo) (1.26.4)\n", + "Requirement already satisfied: requests in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from folium->mesa-geo) (2.32.3)\n", + "Requirement already satisfied: fiona>=1.8.21 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from geopandas->mesa-geo) (1.9.6)\n", + "Requirement already satisfied: packaging in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from geopandas->mesa-geo) (24.0)\n", + "Requirement already satisfied: pandas>=1.4.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from geopandas->mesa-geo) (2.2.2)\n", + "Requirement already satisfied: certifi in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from pyproj->mesa-geo) (2024.2.2)\n", + "Requirement already satisfied: ipywidgets<9,>=7.6.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipyleaflet->mesa-geo) (8.1.3)\n", + "Requirement already satisfied: jupyter-leaflet<0.20,>=0.19 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipyleaflet->mesa-geo) (0.19.1)\n", + "Requirement already satisfied: traittypes<3,>=0.2.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipyleaflet->mesa-geo) (0.2.1)\n", + "Requirement already satisfied: beautifulsoup4>=4.10 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from libpysal->mesa-geo) (4.12.3)\n", + "Requirement already satisfied: platformdirs>=2.0.2 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from libpysal->mesa-geo) (4.2.2)\n", + "Requirement already satisfied: scipy>=1.8 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from libpysal->mesa-geo) (1.13.1)\n", + "Requirement already satisfied: scikit-learn>=1.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from libpysal->mesa-geo) (1.5.0)\n", + "Requirement already satisfied: click in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa->mesa-geo) (8.1.7)\n", + "Requirement already satisfied: cookiecutter in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa->mesa-geo) (2.6.0)\n", + "Requirement already satisfied: matplotlib in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa->mesa-geo) (3.9.0)\n", + "Requirement already satisfied: mesa-viz-tornado>=0.1.3,~=0.1.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa->mesa-geo) (0.1.3)\n", + "Requirement already satisfied: networkx in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa->mesa-geo) (3.3)\n", + "Requirement already satisfied: solara in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa->mesa-geo) (1.37.1)\n", + "Requirement already satisfied: tqdm in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa->mesa-geo) (4.66.4)\n", + "Requirement already satisfied: affine in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from rasterio->mesa-geo) (2.4.0)\n", + "Requirement already satisfied: attrs in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from rasterio->mesa-geo) (23.2.0)\n", + "Requirement already satisfied: cligj>=0.5 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from rasterio->mesa-geo) (0.7.2)\n", + "Requirement already satisfied: snuggs>=1.4.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from rasterio->mesa-geo) (1.4.7)\n", + "Requirement already satisfied: click-plugins in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from rasterio->mesa-geo) (1.1.1)\n", + "Requirement already satisfied: setuptools in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from rasterio->mesa-geo) (70.0.0)\n", + "Requirement already satisfied: soupsieve>1.2 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from beautifulsoup4>=4.10->libpysal->mesa-geo) (2.5)\n", + "Requirement already satisfied: colorama in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from click->mesa->mesa-geo) (0.4.6)\n", + "Requirement already satisfied: six in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from fiona>=1.8.21->geopandas->mesa-geo) (1.16.0)\n", + "Requirement already satisfied: comm>=0.1.3 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipywidgets<9,>=7.6.0->ipyleaflet->mesa-geo) (0.2.2)\n", + "Requirement already satisfied: ipython>=6.1.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipywidgets<9,>=7.6.0->ipyleaflet->mesa-geo) (8.25.0)\n", + "Requirement already satisfied: traitlets>=4.3.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipywidgets<9,>=7.6.0->ipyleaflet->mesa-geo) (5.14.3)\n", + "Requirement already satisfied: widgetsnbextension~=4.0.11 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipywidgets<9,>=7.6.0->ipyleaflet->mesa-geo) (4.0.11)\n", + "Requirement already satisfied: jupyterlab-widgets~=3.0.11 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipywidgets<9,>=7.6.0->ipyleaflet->mesa-geo) (3.0.11)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from jinja2>=2.9->folium->mesa-geo) (2.1.5)\n", + "Requirement already satisfied: tornado in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from mesa-viz-tornado>=0.1.3,~=0.1.0->mesa->mesa-geo) (6.4)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from pandas>=1.4.0->geopandas->mesa-geo) (2.9.0)\n", + "Requirement already satisfied: pytz>=2020.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from pandas>=1.4.0->geopandas->mesa-geo) (2024.1)\n", + "Requirement already satisfied: tzdata>=2022.7 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from pandas>=1.4.0->geopandas->mesa-geo) (2024.1)\n", + "Requirement already satisfied: charset-normalizer<4,>=2 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from requests->folium->mesa-geo) (3.3.2)\n", + "Requirement already satisfied: idna<4,>=2.5 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from requests->folium->mesa-geo) (3.7)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from requests->folium->mesa-geo) (2.2.1)\n", + "Requirement already satisfied: joblib>=1.2.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from scikit-learn>=1.1->libpysal->mesa-geo) (1.4.2)\n", + "Requirement already satisfied: threadpoolctl>=3.1.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from scikit-learn>=1.1->libpysal->mesa-geo) (3.5.0)\n", + "Requirement already satisfied: pyparsing>=2.1.6 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from snuggs>=1.4.1->rasterio->mesa-geo) (3.1.2)\n", + "Requirement already satisfied: binaryornot>=0.4.4 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from cookiecutter->mesa->mesa-geo) (0.4.4)\n", + "Requirement already satisfied: pyyaml>=5.3.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from cookiecutter->mesa->mesa-geo) (6.0.1)\n", + "Requirement already satisfied: python-slugify>=4.0.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from cookiecutter->mesa->mesa-geo) (8.0.4)\n", + "Requirement already satisfied: arrow in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from cookiecutter->mesa->mesa-geo) (1.3.0)\n", + "Requirement already satisfied: rich in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from cookiecutter->mesa->mesa-geo) (13.7.1)\n", + "Requirement already satisfied: contourpy>=1.0.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from matplotlib->mesa->mesa-geo) (1.2.1)\n", + "Requirement already satisfied: cycler>=0.10 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from matplotlib->mesa->mesa-geo) (0.12.1)\n", + "Requirement already satisfied: fonttools>=4.22.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from matplotlib->mesa->mesa-geo) (4.53.0)\n", + "Requirement already satisfied: kiwisolver>=1.3.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from matplotlib->mesa->mesa-geo) (1.4.5)\n", + "Requirement already satisfied: pillow>=8 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from matplotlib->mesa->mesa-geo) (10.3.0)\n", + "Requirement already satisfied: solara-server==1.37.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-server[dev,starlette]==1.37.1->solara->mesa->mesa-geo) (1.37.1)\n", + "Requirement already satisfied: solara-ui==1.37.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-ui[all]==1.37.1->solara->mesa->mesa-geo) (1.37.1)\n", + "Requirement already satisfied: filelock in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa->mesa-geo) (3.14.0)\n", + "Requirement already satisfied: ipykernel in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa->mesa-geo) (6.29.3)\n", + "Requirement already satisfied: jupyter-client>=7.0.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa->mesa-geo) (8.6.2)\n", + "Requirement already satisfied: nbformat in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa->mesa-geo) (5.10.4)\n", + "Requirement already satisfied: rich-click in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa->mesa-geo) (1.8.2)\n", + "Requirement already satisfied: watchdog in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-server[dev,starlette]==1.37.1->solara->mesa->mesa-geo) (4.0.1)\n", + "Requirement already satisfied: watchfiles in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-server[dev,starlette]==1.37.1->solara->mesa->mesa-geo) (0.22.0)\n", + "Requirement already satisfied: starlette in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-server[dev,starlette]==1.37.1->solara->mesa->mesa-geo) (0.37.2)\n", + "Requirement already satisfied: uvicorn in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-server[dev,starlette]==1.37.1->solara->mesa->mesa-geo) (0.30.1)\n", + "Requirement already satisfied: websockets in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-server[dev,starlette]==1.37.1->solara->mesa->mesa-geo) (12.0)\n", + "Requirement already satisfied: humanize in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa->mesa-geo) (4.9.0)\n", + "Requirement already satisfied: ipyvue>=1.9.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa->mesa-geo) (1.11.1)\n", + "Requirement already satisfied: ipyvuetify>=1.6.10 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa->mesa-geo) (1.9.4)\n", + "Requirement already satisfied: reacton>=1.7.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa->mesa-geo) (1.8.3)\n", + "Requirement already satisfied: chardet>=3.0.2 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from binaryornot>=0.4.4->cookiecutter->mesa->mesa-geo) (5.2.0)\n", + "Requirement already satisfied: decorator in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipython>=6.1.0->ipywidgets<9,>=7.6.0->ipyleaflet->mesa-geo) (5.1.1)\n", + "Requirement already satisfied: jedi>=0.16 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipython>=6.1.0->ipywidgets<9,>=7.6.0->ipyleaflet->mesa-geo) (0.19.1)\n", + "Requirement already satisfied: matplotlib-inline in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipython>=6.1.0->ipywidgets<9,>=7.6.0->ipyleaflet->mesa-geo) (0.1.7)\n", + "Requirement already satisfied: prompt-toolkit<3.1.0,>=3.0.41 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipython>=6.1.0->ipywidgets<9,>=7.6.0->ipyleaflet->mesa-geo) (3.0.46)\n", + "Requirement already satisfied: pygments>=2.4.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipython>=6.1.0->ipywidgets<9,>=7.6.0->ipyleaflet->mesa-geo) (2.18.0)\n", + "Requirement already satisfied: stack-data in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipython>=6.1.0->ipywidgets<9,>=7.6.0->ipyleaflet->mesa-geo) (0.6.2)\n", + "Requirement already satisfied: text-unidecode>=1.3 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from python-slugify>=4.0.0->cookiecutter->mesa->mesa-geo) (1.3)\n", + "Requirement already satisfied: types-python-dateutil>=2.8.10 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from arrow->cookiecutter->mesa->mesa-geo) (2.9.0.20240316)\n", + "Requirement already satisfied: markdown-it-py>=2.2.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from rich->cookiecutter->mesa->mesa-geo) (3.0.0)\n", + "Requirement already satisfied: parso<0.9.0,>=0.8.3 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from jedi>=0.16->ipython>=6.1.0->ipywidgets<9,>=7.6.0->ipyleaflet->mesa-geo) (0.8.4)\n", + "Requirement already satisfied: jupyter-core!=5.0.*,>=4.12 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from jupyter-client>=7.0.0->solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa->mesa-geo) (5.7.2)\n", + "Requirement already satisfied: pyzmq>=23.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from jupyter-client>=7.0.0->solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa->mesa-geo) (26.0.3)\n", + "Requirement already satisfied: mdurl~=0.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from markdown-it-py>=2.2.0->rich->cookiecutter->mesa->mesa-geo) (0.1.2)\n", + "Requirement already satisfied: wcwidth in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from prompt-toolkit<3.1.0,>=3.0.41->ipython>=6.1.0->ipywidgets<9,>=7.6.0->ipyleaflet->mesa-geo) (0.2.13)\n", + "Requirement already satisfied: typing-extensions>=4.1.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from reacton>=1.7.1->solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa->mesa-geo) (4.12.1)\n", + "Requirement already satisfied: debugpy>=1.6.5 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipykernel->solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa->mesa-geo) (1.8.1)\n", + "Requirement already satisfied: nest-asyncio in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipykernel->solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa->mesa-geo) (1.6.0)\n", + "Requirement already satisfied: psutil in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from ipykernel->solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa->mesa-geo) (5.9.8)\n", + "Requirement already satisfied: fastjsonschema>=2.15 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from nbformat->solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa->mesa-geo) (2.19.1)\n", + "Requirement already satisfied: jsonschema>=2.6 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from nbformat->solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa->mesa-geo) (4.22.0)\n", + "Requirement already satisfied: cachetools in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa->mesa-geo) (5.3.3)\n", + "Requirement already satisfied: markdown in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa->mesa-geo) (3.6)\n", + "Requirement already satisfied: pymdown-extensions in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from solara-ui==1.37.1->solara-ui[all]==1.37.1->solara->mesa->mesa-geo) (10.8.1)\n", + "Requirement already satisfied: executing>=1.2.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from stack-data->ipython>=6.1.0->ipywidgets<9,>=7.6.0->ipyleaflet->mesa-geo) (2.0.1)\n", + "Requirement already satisfied: asttokens>=2.1.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from stack-data->ipython>=6.1.0->ipywidgets<9,>=7.6.0->ipyleaflet->mesa-geo) (2.4.1)\n", + "Requirement already satisfied: pure-eval in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from stack-data->ipython>=6.1.0->ipywidgets<9,>=7.6.0->ipyleaflet->mesa-geo) (0.2.2)\n", + "Requirement already satisfied: anyio<5,>=3.4.0 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from starlette->solara-server[dev,starlette]==1.37.1->solara->mesa->mesa-geo) (4.3.0)\n", + "Requirement already satisfied: h11>=0.8 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from uvicorn->solara-server[dev,starlette]==1.37.1->solara->mesa->mesa-geo) (0.14.0)\n", + "Requirement already satisfied: sniffio>=1.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from anyio<5,>=3.4.0->starlette->solara-server[dev,starlette]==1.37.1->solara->mesa->mesa-geo) (1.3.1)\n", + "Requirement already satisfied: jsonschema-specifications>=2023.03.6 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from jsonschema>=2.6->nbformat->solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa->mesa-geo) (2023.12.1)\n", + "Requirement already satisfied: referencing>=0.28.4 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from jsonschema>=2.6->nbformat->solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa->mesa-geo) (0.35.1)\n", + "Requirement already satisfied: rpds-py>=0.7.1 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from jsonschema>=2.6->nbformat->solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa->mesa-geo) (0.18.1)\n", + "Requirement already satisfied: pywin32>=300 in c:\\users\\thoma\\miniconda3\\envs\\mesa_geo\\lib\\site-packages (from jupyter-core!=5.0.*,>=4.12->jupyter-client>=7.0.0->solara-server==1.37.1->solara-server[dev,starlette]==1.37.1->solara->mesa->mesa-geo) (306)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "A subdirectory or file data already exists.\n", + "Error occurred while processing: data.\n", + "'wget' is not recognized as an internal or external command,\n", + "operable program or batch file.\n" + ] + } + ], "source": [ - "# Introductory Tutorial" + "#Run this if in colab\n", + "!pip install mesa\n", + "!pip install mesa-geo\n", + "!mkdir -p data\n", + "!wget -P data https://raw.githubusercontent.com/projectmesa/mesa-geo/tree/main/docs/tutorials/data/TorontoNeighbourhoods.geojson" ] }, { "cell_type": "markdown", - "id": "1482bfa9", + "id": "3f1b8324-9849-4992-b13a-db9a94372d0a", "metadata": {}, "source": [ - "## Getting started\n", + "## Part 1 Create the Basic Model\n", + "This portion initializes the human agents, the neighborhood agents, and the model class that manages the model dynamics. \n", "\n", - "You should be familiar with how [Mesa](https://github.com/projectmesa/mesa) works.\n", + "First we import our dependencies" + ] + }, + { + "cell_type": "markdown", + "id": "431d4b10-a0ee-4c03-9197-1c1266165169", + "metadata": { + "explanatory": true, + "has_explanation": false + }, + "source": [ + "\n", + "\n", + "This cell imports the specific libraries we need to create our model. \n", "\n", - "So let's get started with some geometries! We will work with [records of US states](http://eric.clst.org/Stuff/USGeoJSON). We use the `requests` library to retrieve the data, but of course you can work with local data." + "- [Shapley](https://shapely.readthedocs.io/en/stable/) a library GIS library for object in the cartesian plane. From Shapely we specifically need the Point class to create our human agents\n", + "- [Mesa](https://mesa.readthedocs.io/en/stable/) the parent ABM library to Mesa-Geo\n", + "\n", + "Then of course mesa-geo which although not strictly necessary we also specifically import the visualization part of the library \n", + "so we do not have to write out mesa-geo.visualization modules when we call them. " ] }, { "cell_type": "code", - "execution_count": null, - "id": "6c026625", + "execution_count": 3, + "id": "c7d43c11-a846-45e1-bbcf-77de58ff5033", "metadata": { - "ExecuteTime": { - "end_time": "2022-10-17T18:18:56.247846Z", - "start_time": "2022-10-17T18:18:29.927694Z" - }, + "has_explanation": true + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\thoma\\miniconda3\\envs\\mesa_geo\\Lib\\site-packages\\solara\\validate_hooks.py:122: UserWarning: C:\\Users\\thoma\\miniconda3\\envs\\mesa_geo\\Lib\\site-packages\\mesa\\experimental\\jupyter_viz.py:163: JupyterViz: `use_state` found within a nested function created on line 155\n", + "To suppress this check, replace the line with:\n", + " grid_layout, set_grid_layout = solara.use_state(grid_layout_initial) # noqa: SH104\n", + "\n", + "Make sure you understand the consequences of this, by reading about the rules of hooks at:\n", + " https://solara.dev/documentation/advanced/understanding/rules-of-hooks\n", + "\n", + " warnings.warn(str(e))\n" + ] + } + ], + "source": [ + "from shapely.geometry import Point\n", + "\n", + "import mesa\n", + "import mesa_geo as mg\n", + "import mesa_geo.visualization as mgv\n" + ] + }, + { + "cell_type": "markdown", + "id": "040912e4-e07b-4ffa-97fa-5aeae014c844", + "metadata": {}, + "source": [ + "### Create the person agent class\n", + "\n", + "The person in this model represents one human being and we initilaize each person agent with two key parts: \n", + "1. The agent attributes, such as recovery rate and death risk\n", + "2. The step function, actions the agent will take each model step " + ] + }, + { + "cell_type": "markdown", + "id": "0d0bd901-c038-40d5-8017-df3fdedbd838", + "metadata": { + "explanatory": true, "has_explanation": false }, + "source": [ + "\n", + "The first thing we are going to do is create the person agent class. This class has several attributes necessary to make a more holistic model. \n", + "\n", + "First, there are the required attributes for any GeoAgent in Mesa-Geo\n", + "\n", + "- **unique_id**: Some unique identifier, often an int, this ensure Mesa can keep track of each agent without confusion\n", + "- **model**: Model object class that we will build later, this is a pointer to the model instance so the agent can get information from the model as it behaves\n", + "- **geometry**: GIS geometric object in this case a GIS Point\n", + "- **crs**: A string describing the coordinate reference system the agent is using\n", + "\n", + "As you can see these are inherited from the mesa-geo librarary through the \"mg.GeoAgent\" in the class instantiation. \n", + "\n", + "Second, the variable attributes these are unique to our SIR model:\n", + "\n", + "- **agent_type**: A string which describes the agent state (susceptible, infected, recovered, or dead) \n", + "- **mobility_range**: Distance the agent can move in meters\n", + "- **infection risk**: A float from 0.0 to 1.0 that determines the risk of the agent being infected if exposed. \n", + "- **recovery_rate**: A float from 0.0 to 1.0 that determine how long the agents takes to recover\n", + "- **death_risk**: A float from 0.0 to 1.0 that determines the probability the agent will die\n", + "\n", + "The **`__repr__`** function is a Python primitive that will print out information as directed by the code. In this case we will print out the agent ID\n", + "\n", + "The **step** function is a Mesa primitive that the scheduler looks for and describes what action the agent takes each step" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "b39b18eb-16e9-4db0-8739-a050cc74d40a", + "metadata": { + "has_explanation": true + }, "outputs": [], "source": [ - "import warnings\n", + "class PersonAgent(mg.GeoAgent):\n", + " \"\"\"Person Agent.\"\"\"\n", "\n", - "warnings.filterwarnings(\"ignore\")\n", + " def __init__(\n", + " self,\n", + " unique_id,\n", + " model,\n", + " geometry,\n", + " crs,\n", + " agent_type,\n", + " mobility_range,\n", + " infection_risk,\n", + " recovery_rate,\n", + " death_risk\n", + " ):\n", + " super().__init__(unique_id, model, geometry, crs)\n", + " # Agent attributes\n", + " self.atype = agent_type\n", + " self.mobility_range = mobility_range\n", + " self.infection_risk=infection_risk,\n", + " self.recovery_rate = recovery_rate\n", + " self.death_risk = death_risk\n", "\n", - "import mesa\n", - "import mesa_geo as mg\n", - "import requests\n", + " def __repr__(self):\n", + " return \"Person \" + str(self.unique_id)\n", "\n", + " def step(self): \n", + " print (repr(self))\n", + " print(self.atype, self.death_risk, self.recovery_rate)" + ] + }, + { + "cell_type": "markdown", + "id": "ce98080f-209d-4476-b50c-88a232ff0bbb", + "metadata": {}, + "source": [ + "### Create the neighborhood agent \n", + "\n", + "The neighborhood in this model represents one geographical area as defined by the geojson file we uploaded. \n", "\n", - "url = \"http://eric.clst.org/assets/wiki/uploads/Stuff/gz_2010_us_040_00_20m.json\"\n", - "r = requests.get(url)\n", - "geojson_states = r.json()" + "Similar to the person agent, we initialize each neighborhood agent with the same two key parts. \n", + "\n", + "1. The agent attributes, such as geometry and state of neighborhood\n", + "2. The step function, behaviors the agent will take during each model step. " ] }, { "cell_type": "markdown", - "id": "c5599433", + "id": "b44f5943-ae82-4329-9255-9ec8458311aa", "metadata": { - "ExecuteTime": { - "end_time": "2022-08-31T13:29:13.715839Z", - "start_time": "2022-08-31T13:29:13.710995Z" - } + "explanatory": true }, "source": [ - "First we create a `State` Agent and a `GeoModel`. Both should look familiar if you have worked with Mesa before." + "\n", + "Similar to the person agent for the neighborhood agent there are two types of attributes. \n", + "\n", + "The required attributes for any GeoAgent in Mesa-Geo:\n", + "\n", + "- **unique_id**: For geographic agents such as a neighborhood mesa-geo will assign a very large integer as the agent id, if desired users can specify their own. \n", + "- **model**: Model object class that we will build later, this is a pointer to the model instance so the agent can get information from the model as it behaves\n", + "- **geometry**: GIS geometric object in this case a polygon form the geojson defining the perimeter of the neighborhood\n", + "- **crs**: A string describing the coordinate reference system the agent is using\n", + "\n", + "Similar to the person agent, \"mg.GeoAgent\" is inherited from mesa-geo. \n", + "\n", + "Next are the variable attributes:\n", + "\n", + "- **agent_type**: A string which describes the state of the neighborhood which will be either safe or hot spot \n", + "- **hotspot_threshold**: An integer that is the number of infected people in a neighborhood to call it a hotspot \n", + "\n", + "We will also use the **`__repr__`** function to print out the agent ID\n", + "\n", + "Then the **step** function, which is a primitive that the Mesa scheduler looks for and describes what action the agent takes each step" ] }, { "cell_type": "code", - "execution_count": null, - "id": "c07e7a9a", + "execution_count": 5, + "id": "da9c0121-9f35-4c7b-ab6a-d8d970189ec6", "metadata": { - "ExecuteTime": { - "end_time": "2022-10-17T18:18:56.256884Z", - "start_time": "2022-10-17T18:18:56.251192Z" - }, - "has_explanation": false + "has_explanation": true }, "outputs": [], "source": [ - "class State(mg.GeoAgent):\n", - " def __init__(self, unique_id, model, geometry, crs):\n", + "class NeighbourhoodAgent(mg.GeoAgent):\n", + " \"\"\"Neighbourhood agent. Changes color according to number of infected inside it.\"\"\"\n", + "\n", + " def __init__(\n", + " self, unique_id, model, geometry, crs, agent_type=\"safe\", hotspot_threshold=1\n", + " ):\n", " super().__init__(unique_id, model, geometry, crs)\n", + " self.atype = agent_type\n", + " self.hotspot_threshold = (\n", + " hotspot_threshold # When a neighborhood is considered a hot-spot\n", + " )\n", "\n", + " def __repr__(self):\n", + " return \"Neighbourhood \" + str(self.unique_id)\n", + " \n", + " def step(self):\n", + " \"\"\"Advance agent one step.\"\"\"\n", + " print(repr(self))\n", + " \n", + " " + ] + }, + { + "cell_type": "markdown", + "id": "efe370a7-a27e-4c76-830d-155a8853d81c", + "metadata": {}, + "source": [ + "### Create the Model Class\n", "\n", - "class GeoModel(mesa.Model):\n", - " def __init__(self):\n", - " self.space = mg.GeoSpace()\n", + "The model class is the manager that instantiates the agents, then manages what is happening in the model through the step function, and collects data. \n", "\n", - " ac = mg.AgentCreator(agent_class=State, model=self)\n", - " agents = ac.from_GeoJSON(GeoJSON=geojson_states, unique_id=\"NAME\")\n", - " self.space.add_agents(agents)" + "We will create the model with parameters that will set the attributes of the agents as it instantiates them and a step function to call the agent step function. " ] }, { "cell_type": "markdown", - "id": "c806fc00", - "metadata": {}, + "id": "c44bdd40-6ee9-4579-90fa-502ba482f157", + "metadata": { + "explanatory": true + }, "source": [ - "In the `GeoModel` we first create an instance of AgentCreator, where we provide the Agent class (State) and its required arguments, except geometry and unique_id. We then use the `.from_GeoJSON` function to create our agents from the geometries in the GeoJSON file. We provide the feature \"name\" as the key from which the agents get their unique_ids.\n", - "Finally, we add the agents to the GeoSpace\n", "\n", - "Let's instantiate our model and look at one of the agents:" + "First, we name our class in this case GeoSIR and we inherit the model class from Mesa. We store the path to our GeoJSON file in the object geojson regions. As JSONs mirror Pythons dictionary structure, we store the key for the neighbourhood id (\"HOODNUM\") in the variable unique_id. \n", + "\n", + "Second, we set up the python initializer to initiate our model class. To do this we will, set up key word arguments or kwargs of the parameters we want for our model. In this case we will use: \n", + "- population size (pop_size): An integer that determines the number of person agents\n", + "- initial infection (init_infection): A float between 0.0 and 1.0 which determines what percentage of the population is infected as the model initiates\n", + "- exposure_distance (exposure_dist): An integer for the distance in meters a susceptible person agent must within to be infected by a person agent who is infected\n", + "- maximum infection risk (max_infection_risk): A float between 0.0 and 1.0 of which determines the highest suscpetibility rate in the population\n", + "\n", + "Third, we initialize our agents. Mesa-Geo has an AgentCreator class inside is geoagent.py file that can create GeoAgents from files, GeoDataFrames, GeoJSON or Shapely objects. \n", + "\n", + "**Creating the NeighbourhoodAgents**\n", + "\n", + "In this case we will use the `torontoneighbourhoods.geojson` file located in the data folder to to create the NeighbourhoodAgents. Next, we will add them to the environment with the space.add_agents function. Then we will iterate through each of the NeighbourhoodAgents to add them to the schedule. \n", + "\n", + "\n", + "**Creating the PersonAgents**\n", + "\n", + "We will use Mesa-Geo AgentCreator to create the person agents. To create a heterogenous (diverse) population we will use the [random object created as part of Mesa's base class](https://github.com/projectmesa/mesa/blob/01477fc9624b70078fe1c82634d7c9e4938de942/mesa/model.py#L60) to help intialize the population's parameters. \n", + "\n", + "- death_risk: A float from 0 to 1\n", + "- agent_type: Compares the model parameter of intial infection of a random float between 0 and 1 and the initial infection parameter. If it is less than the initial infection parameter the agent is initialized as infected.\n", + "- recover: Is an integer between 1 and the recovery rate. This determines the number of steps it takes for the agent to recover.\n", + "- infection_risk: is a float between 0 and the parameter of max_infection_risk, which will then determine how likely a person is to get infected.\n", + "- death_risk: Is a random float between 0 and 1 that will determine how likely a person is to die when infected.\n", + "\n", + "By using Python's random library to create these attributes for each agent, we can now create a diverse agent population. \n", + "\n", + "Passing these parameters through the `AgentCreator` class we initialize our agent object. \n", + "\n", + "As Mesa-Geo is an GIS based ABM, we need assign each PersonAgent a Geometry and location. To do this we will use a helper function `find_home`. This helper function first identifies a NeighbourhoodAgent where the PersonAgent will start. Next it identifies the center of the neighborhood and its boundary and then randomly moving from the center point, put staying wihtin the bounds, it a lat and long to aissgn the PersonAgent is starting location.\n", + "\n", + "**Step Function**\n", + "\n", + "The final piece is to initialize a step function. This function a Mesa primitive calls the RandomActiviationByType scheduler we set up and then iterates through each agent calling their step function. \n", + "\n", + "**The Model**\n", + "\n", + "We know have the pieces of our Model. A GIS layer of polygons that creates NeighbourhoodAgents from our GeoJSON file. A diverse population of GIS Point objects, with different infection, recovery and death risks. A model class that initializes these agents, a scheduler to call these agents, a GIS space and step function to execute the simulation\n" ] }, { "cell_type": "code", - "execution_count": null, - "id": "ebee624a", + "execution_count": 6, + "id": "d80d198b-6fa3-4be3-ad05-213d70e7a093", "metadata": { - "ExecuteTime": { - "end_time": "2022-10-17T18:18:56.466576Z", - "start_time": "2022-10-17T18:18:56.260255Z" - }, - "has_explanation": false + "has_explanation": true }, "outputs": [], "source": [ - "m = GeoModel()\n", + "class GeoSIR(mesa.Model):\n", + " \"\"\"Model class for a simplistic infection model.\"\"\"\n", + "\n", + " # Geographical parameters for desired map\n", + " geojson_regions = \"data/TorontoNeighbourhoods.geojson\"\n", + " unique_id = \"HOODNUM\"\n", + "\n", + " def __init__(\n", + " self, pop_size=30, mobility_range=500, init_infection=0.2, exposure_dist=500, max_infection_risk=0.2,\n", + " max_recovery_time=5\n", + " ):\n", + " self.schedule = mesa.time.RandomActivationByType(self)\n", + " self.space = mg.GeoSpace(warn_crs_conversion=False)\n", + " \n", + " # SIR model parameters\n", + " self.pop_size = pop_size\n", + " self.mobility_range = mobility_range\n", + " self.initial_infection = init_infection\n", + " self.exposure_distance = exposure_dist\n", + " self.infection_risk = max_infection_risk\n", + " self.recovery_rate = max_recovery_time\n", + "\n", + " # Set up the Neighbourhood patches for every region in file\n", + " ac = mg.AgentCreator(NeighbourhoodAgent, model=self)\n", + " neighbourhood_agents = ac.from_file(\n", + " self.geojson_regions, unique_id=self.unique_id\n", + " )\n", + " \n", + " #Add neighbourhood agents to space\n", + " self.space.add_agents(neighbourhood_agents)\n", + " \n", + " #Add neighbourhood agents to scheduler \n", + " for agent in neighbourhood_agents:\n", + " self.schedule.add(agent)\n", + " \n", + " \n", + " # Generate random location, add agent to grid and scheduler\n", + " for i in range(pop_size):\n", + " #assess if they are infected\n", + " if self.random.random() < self.initial_infection: \n", + " agent_type = \"infected\"\n", + " else: \n", + " agent_type = \"susceptible\"\n", + " #determine movement range\n", + " mobility_range = self.random.randint(0,self.mobility_range) \n", + " #determine agent recovery rate\n", + " recover = self.random.randint(1,self.recovery_rate)\n", + " #determine agents infection risk\n", + " infection_risk = self.random.uniform(0,self.infection_risk)\n", + " #determine agent death probability \n", + " death_risk= self.random.random()\n", "\n", - "agent = m.space.agents[0]\n", - "print(agent.unique_id)\n", - "agent.geometry" + " # Generate PersonAgent population\n", + " unique_person = mg.AgentCreator(\n", + " PersonAgent,\n", + " model=self,\n", + " crs=self.space.crs,\n", + " agent_kwargs={\"agent_type\": agent_type, \n", + " \"mobility_range\":mobility_range,\n", + " \"recovery_rate\":recover,\n", + " \"infection_risk\": infection_risk,\n", + " \"death_risk\": death_risk\n", + " }\n", + " )\n", + " \n", + " \n", + " x_home, y_home = self.find_home(neighbourhood_agents)\n", + " \n", + " this_person = unique_person.create_agent(\n", + " Point(x_home, y_home), \"P\" + str(i), \n", + " )\n", + " self.space.add_agents(this_person)\n", + " self.schedule.add(this_person)\n", + " \n", + " \n", + " def find_home(self, neighbourhood_agents): \n", + " \"\"\" Find start location of agent \"\"\"\n", + "\n", + " #identify location\n", + " this_neighbourhood = self.random.randint(\n", + " 0, len(neighbourhood_agents) - 1\n", + " ) # Region where agent starts\n", + " center_x, center_y = neighbourhood_agents[\n", + " this_neighbourhood\n", + " ].geometry.centroid.coords.xy\n", + " this_bounds = neighbourhood_agents[this_neighbourhood].geometry.bounds\n", + " spread_x = int(\n", + " this_bounds[2] - this_bounds[0]\n", + " ) # Heuristic for agent spread in region\n", + " spread_y = int(this_bounds[3] - this_bounds[1])\n", + " this_x = center_x[0] + self.random.randint(0, spread_x) - spread_x / 2\n", + " this_y = center_y[0] + self.random.randint(0, spread_y) - spread_y / 2\n", + "\n", + " return this_x, this_y\n", + "\n", + " \n", + " def step(self):\n", + " \"\"\"Run one step of the model.\"\"\"\n", + " self.schedule.step()\n" ] }, { "cell_type": "markdown", - "id": "983dad91", + "id": "eb15ce7d-3537-4e23-84da-dca93ad7c3f9", "metadata": {}, "source": [ - "If you work in the Jupyter Notebook your output should give you the name of the state and a visual representation of the geometry.\n", + "### Run The Base Model" + ] + }, + { + "cell_type": "markdown", + "id": "36bdf628-b152-45ec-a568-03e6faa413e9", + "metadata": { + "explanatory": true + }, + "source": [ + "#explanatory\n", "\n", - "By default the AgentCreator also sets further agent attributes from the Feature properties." + "This cell is fairly simple\n", + "\n", + "1 - We instantiate the SIR model by call the class name \"GeoSIR\" into the object `model`.\n", + "\n", + "2 - Then we call the step function to see if it prints out the Agent IDs, infection status, death_risk, and recovery rate as called in the PersonAgent class. \n", + "\n", + "You can also see all the person agents are called and then the neighbourhood agents. This will become important later as we want to update the neighbourhood status later based on its PersonAgent status. \n", + "\n", + "If you are curious about the numbers for the neighbourhood agents, you can open up the GeoJSON in the data folder and see that each neighborhood gets a unique id identified by `HOODNUM` to ensure this number does not cause a conflict with our agent numbers, we add a \"P\" to their ID. \n", + "\n" ] }, { "cell_type": "code", - "execution_count": null, - "id": "0f7c0ee6", + "execution_count": 7, + "id": "c181dbf7-c175-47c7-8c20-2ec8fa758c60", "metadata": { - "ExecuteTime": { - "end_time": "2022-10-17T18:18:56.473375Z", - "start_time": "2022-10-17T18:18:56.469477Z" + "has_explanation": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Neighbourhood 42\n", + "Neighbourhood 105\n", + "Neighbourhood 41\n", + "Neighbourhood 103\n", + "Neighbourhood 40\n", + "Person P23\n", + "susceptible 0.3971871353466606 2\n", + "Person P17\n", + "susceptible 0.10436198865724955 5\n", + "Person P16\n", + "susceptible 0.5339870523330027 3\n", + "Person P4\n", + "susceptible 0.5303144600879167 4\n", + "Person P10\n", + "susceptible 0.9913326378506605 5\n", + "Person P0\n", + "susceptible 0.4023861466910281 2\n", + "Person P26\n", + "infected 0.7275365542013165 5\n", + "Person P5\n", + "susceptible 0.45459766405313096 5\n", + "Person P1\n", + "susceptible 0.27688073788518963 5\n", + "Person P11\n", + "susceptible 0.6719897329852178 3\n", + "Person P9\n", + "susceptible 0.6806206465598639 4\n", + "Person P19\n", + "susceptible 0.1677249281695108 5\n", + "Person P3\n", + "susceptible 0.13387686899329032 3\n", + "Person P29\n", + "susceptible 0.08270609418080976 5\n", + "Person P13\n", + "infected 0.7415282498066829 1\n", + "Person P20\n", + "susceptible 0.5515016677914185 3\n", + "Person P25\n", + "susceptible 0.15463709236464573 2\n", + "Person P15\n", + "susceptible 0.919918572830098 5\n", + "Person P24\n", + "susceptible 0.5186275568645736 5\n", + "Person P22\n", + "susceptible 0.8720559066000902 1\n", + "Person P27\n", + "susceptible 0.3426893827539842 4\n", + "Person P12\n", + "infected 0.019412564602877658 5\n", + "Person P28\n", + "susceptible 0.9219719091009597 5\n", + "Person P7\n", + "susceptible 0.02011650049009317 2\n", + "Person P18\n", + "susceptible 0.3176296257929059 5\n", + "Person P6\n", + "susceptible 0.07092223940518694 2\n", + "Person P2\n", + "susceptible 0.043208096416691766 4\n", + "Person P21\n", + "susceptible 0.1856727135544145 3\n", + "Person P14\n", + "susceptible 0.016180549074910178 5\n", + "Person P8\n", + "susceptible 0.22053356856124806 5\n" + ] }, - "has_explanation": false + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\thoma\\Documents\\GitHub\\dev\\mesa-geo\\mesa_geo\\geoagent.py:38: FutureWarning: The Mesa Model class was not initialized. In the future, you need to explicitly initialize the Model by calling super().__init__() on initialization.\n", + " Agent.__init__(self, unique_id, model)\n" + ] + } + ], + "source": [ + "model = GeoSIR()\n", + "model.step()" + ] + }, + { + "cell_type": "markdown", + "id": "ceeed343-3cdb-45af-be8e-90816f542e4d", + "metadata": {}, + "source": [ + "## Part 2 Add Agent and Model Complexity " + ] + }, + { + "cell_type": "markdown", + "id": "dfbc2440-a9a3-4e40-903e-c0d9ff98c548", + "metadata": {}, + "source": [ + "### Increase PersonAgent Complexity\n", + "\n", + "In this section we add behaviors to the PersonAgent to build the necessary SIR dynamics. " + ] + }, + { + "cell_type": "markdown", + "id": "26967d31-9dab-4a8f-b970-17ab2e1c7990", + "metadata": { + "explanatory": true + }, + "source": [ + "\n", + "To create the SIR dynamics we need the agents move, determine if they have been exposed and if they have process the probability of them being infected and possibly dying. \n", + "\n", + "To do this we will update our step function. The step function logic uses the agent's `atype` to determine what actions to process\n", + "\n", + "**Part 1**\n", + "\n", + "If the PersonAgent `atype` is susceptible, then we need to identify all PersonAgent's neighbors within the exposure distance. To do this, we will use Mesa-Geo's `get_neighbors_within_distance` function which takes 2 parameters, the agent, and a distance, which in this case is the model parameter for exposure distance in meters. This creates a list of PersonAgents within that distance. \n", + "\n", + "The `get_neighbors_within_distance` [function](https://github.com/projectmesa/mesa-geo/blob/56c598486e0f58f3626d9951796998d38bbab0b5/mesa_geo/geospace.py#L197) has two keyword arguments `center` and `relation`. `center` takes `True` or `False` on whether to include the center, it is set to `False` and measures as a buffer around the agent's geometry. If `True` it measures from the Center of the point. `relation` is defaulted to `intersects` but can take any common spatial relationship, such as `contains`, `within`, `touches`, `crosses`\n", + "\n", + "The step function then iterates through the list of neighbors to see if any agents are infected. If so it does a probablistic comparison of a random float compared to the agents infection risk and if `True` the agent becomes infected and the iteration ends. \n", + "\n", + "**Part 2**\n", + "\n", + "If the agent `atype` is infected, then the step function does comparisons. First, it sees how many steps the agent has been infected. To track this the PersonAgent got a new attribute counter which is `steps_infected`. If the steps are greater than or equal to their recovery rate, the agent is recovered, if not then the function does a probablistic comparison with the agents death risk to see if the agent dies. If neither of these things happen the `steps_infected` increases by one.\n", + "\n", + "**Part 3**\n", + "\n", + "The next part is if the agent `atype` is not dead then the agent moves. For this we randomly get an integer for the x any (lat and long) between their negative `mobility_range` and positive `mobility range`. We pass these two integers into the helper function `move_point` and then update the agents geometry with this new point. \n", + "\n", + "Finally, we update the counts of agent types. " + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "0f6e8a2c-ec86-4cd8-8feb-86e3da6920cb", + "metadata": { + "has_explanation": true }, "outputs": [], "source": [ - "agent.CENSUSAREA" + "class PersonAgent(mg.GeoAgent):\n", + " \"\"\"Person Agent.\"\"\"\n", + "\n", + " def __init__(\n", + " self,\n", + " unique_id,\n", + " model,\n", + " geometry,\n", + " crs,\n", + " agent_type,\n", + " mobility_range,\n", + " infection_risk,\n", + " recovery_rate,\n", + " death_risk\n", + " ):\n", + " super().__init__(unique_id, model, geometry, crs)\n", + " # Agent attributes\n", + " self.atype = agent_type\n", + " self.mobility_range = mobility_range\n", + " self.infection_risk=infection_risk,\n", + " self.recovery_rate = recovery_rate\n", + " self.death_risk = death_risk\n", + " self.steps_infected=0\n", + " self.steps_recovered = 0\n", + "\n", + " def __repr__(self):\n", + " return \"Person \" + str(self.unique_id)\n", + "\n", + " #Helper function for moving agent\n", + " def move_point(self, dx, dy):\n", + " \"\"\"\n", + " Move a point by creating a new one\n", + " :param dx: Distance to move in x-axis\n", + " :param dy: Distance to move in y-axis\n", + " \"\"\"\n", + " return Point(self.geometry.x + dx, self.geometry.y + dy)\n", + " \n", + " \n", + " def step(self): \n", + "\n", + " #Part 1 - find neighbors based on infection distance\n", + " if self.atype == \"susceptible\":\n", + " neighbors = self.model.space.get_neighbors_within_distance(\n", + " self, self.model.exposure_distance\n", + " )\n", + " for neighbor in neighbors:\n", + " if (\n", + " neighbor.atype == \"infected\"\n", + " and self.random.random() < self.model.infection_risk\n", + " ):\n", + " self.atype = \"infected\"\n", + " break #stop process if agent becomes infected\n", + "\n", + " #Part -2 If infected, check if agent recovers or agent dies\n", + " elif self.atype == \"infected\":\n", + " if self.steps_infected >= self.recovery_rate:\n", + " self.atype = \"recovered\"\n", + " self.steps_infected = 0\n", + " elif self.random.random() < self.death_risk:\n", + " self.atype = \"dead\"\n", + " else:\n", + " self.steps_infected += 1\n", + "\n", + " elif self.atype == \"recovered\":\n", + " self.steps_recovered+=1\n", + " if self.steps_recovered >=2: \n", + " self.atype= \"susceptible\"\n", + " self.steps_recovered = 0\n", + " \n", + " #Part 3 - If not dead, move\n", + " if self.atype != \"dead\":\n", + " move_x = self.random.randint(-self.mobility_range, self.mobility_range)\n", + " move_y = self.random.randint(-self.mobility_range, self.mobility_range)\n", + " self.geometry = self.move_point(move_x, move_y) # Reassign geometry\n", + "\n", + " self.model.counts[self.atype] += 1 # Count agent type" ] }, { "cell_type": "markdown", - "id": "6a843f67", + "id": "c4823f40-2837-4176-8ee5-e09276888e45", "metadata": {}, "source": [ - "Let's start to do some spatial analysis. We can use usual Mesa function names to get neighboring states." + "### Increase NeighbourhoodAgent Complexity" + ] + }, + { + "cell_type": "markdown", + "id": "e1af08cc-4f1e-45b7-8282-06d30f6dbe3e", + "metadata": { + "explanatory": true + }, + "source": [ + "\n", + "For the NeighbourhoodAgent we want to change their color based on the number of infected PersonAgents in their neighourhood. \n", + "\n", + "To do this we will create a helper function called `color_hotspot`. We will then use mesa-geo's `get_intersecting_agents` [function](https://github.com/projectmesa/mesa-geo/blob/56c598486e0f58f3626d9951796998d38bbab0b5/mesa_geo/geospace.py#L194). We will then iterate through that list to get the agents with `atype` infected if the list is longer than our `hotspot_threshold` equal to 1 (so if two agents in the neighborhood are infected) then the `atype` will change to `hotspot`. \n", + "\n", + "We then update our model counts. " ] }, { "cell_type": "code", - "execution_count": null, - "id": "154e56b2", + "execution_count": 9, + "id": "50aab416-959f-4d72-abd9-844ed42c784d", "metadata": { - "ExecuteTime": { - "end_time": "2022-10-17T18:18:56.759515Z", - "start_time": "2022-10-17T18:18:56.475418Z" - }, - "has_explanation": false + "has_explanation": true, + "jupyter": { + "source_hidden": true + } }, "outputs": [], "source": [ - "neighbors = m.space.get_neighbors(agent)\n", - "print([a.unique_id for a in neighbors])" + "class NeighbourhoodAgent(mg.GeoAgent):\n", + " \"\"\"Neighbourhood agent. Changes color according to number of infected inside it.\"\"\"\n", + "\n", + " def __init__(\n", + " self, unique_id, model, geometry, crs, agent_type=\"safe\", hotspot_threshold=1\n", + " ):\n", + " super().__init__(unique_id, model, geometry, crs)\n", + " self.atype = agent_type\n", + " self.hotspot_threshold = (\n", + " hotspot_threshold # When a neighborhood is considered a hot-spot\n", + " )\n", + "\n", + " def __repr__(self):\n", + " return \"Neighbourhood \" + str(self.unique_id)\n", + " \n", + " def color_hotspot(self):\n", + " # Decide if this region agent is a hot-spot\n", + " # (if more than threshold person agents are infected)\n", + " neighbors = self.model.space.get_intersecting_agents(self)\n", + " infected_neighbors = [\n", + " neighbor for neighbor in neighbors if neighbor.atype == \"infected\"\n", + " ]\n", + " if len(infected_neighbors) > self.hotspot_threshold:\n", + " self.atype = \"hotspot\"\n", + " else:\n", + " self.atype = \"safe\"\n", + " \n", + " def step(self):\n", + " \"\"\"Advance agent one step.\"\"\"\n", + " self.color_hotspot()\n", + " self.model.counts[self.atype] += 1 # Count agent type" ] }, { "cell_type": "markdown", - "id": "6b5d5c9d", + "id": "ee5ba9f7-db79-43a9-8a76-37dfbf6b2bd5", "metadata": {}, "source": [ - "To get a list of all states within a certain distance you can use the following:" + "### Increase model complexity\n", + "\n", + "For this section will add data collection where we collect the status of the PersonAgents and the NeighbourhoodAgents but counting the different `atypes`.\n" + ] + }, + { + "cell_type": "markdown", + "id": "6ccbc3cb-083d-4fc1-9516-85a71fe36701", + "metadata": { + "explanatory": true + }, + "source": [ + "\n", + "As we run our SIR model, we want to ensure we are collecting information about the status of the disease. \n", + "\n", + "To do this we will create helper functions that get htis information. In this case we will put them in a seperate cell, but depending on the developers preference they could also put them in the model class or collect the information in a handful of other ways. \n", + "\n", + "In this case, we set up an attribute in the model called counts and these functions just get the total number from Mesa's data collector of each of our statuses. " ] }, { "cell_type": "code", - "execution_count": null, - "id": "63b36a85", + "execution_count": 10, + "id": "5e93ff02-3d17-4663-a33a-5113e65c6aef", "metadata": { - "ExecuteTime": { - "end_time": "2022-10-17T18:18:56.769281Z", - "start_time": "2022-10-17T18:18:56.761925Z" - }, - "has_explanation": false + "has_explanation": true + }, + "outputs": [], + "source": [ + "# Functions needed for datacollector\n", + "def get_infected_count(model):\n", + " return model.counts[\"infected\"]\n", + "\n", + "\n", + "def get_susceptible_count(model):\n", + " return model.counts[\"susceptible\"]\n", + "\n", + "\n", + "def get_recovered_count(model):\n", + " return model.counts[\"recovered\"]\n", + "\n", + "\n", + "def get_dead_count(model):\n", + " return model.counts[\"dead\"]\n", + "\n", + "def get_hotspot_count(model): \n", + " return model.counts[\"hotspot\"]\n", + "\n", + "def get_safe_count(model): \n", + " return model.counts[\"safe\"]" + ] + }, + { + "cell_type": "markdown", + "id": "9ef38b13-a442-480d-ab23-0fa4366dd716", + "metadata": { + "explanatory": true + }, + "source": [ + "\n", + "Now to finish the model so we can add the interface we add datacollection and a stop condition. As these updates are interspersed throughout the class. The comment `#added` is used to make the changes easier to identify.\n", + "\n", + "First, we add an attribute called `self.counts` which will track our the agent types (e.g. infected). We will initialize it as None. We then initialize the counts in our next line `self.reset_counts()`. This helper function located directly above the step function, resets the counts of each type of agent so it is always based on the current situation in the Model. \n", + "\n", + "We are then going to add the attribute self.running so we can input the stop condition. Next we set our our data collector that call our functions from the previous cell which collects our agent types\n", + "\n", + "With these added we can now call `self.reset_counts` and `self.datacollector.collect` in our step function so it collect our agent states each step. \n", + "\n", + "Finally we add a stop condition. If no PersonAgent is infected the pandemic is over and we stop the model. \n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "d24aa5cb-61bc-4651-9792-3ab68ff3b0f6", + "metadata": { + "has_explanation": true }, "outputs": [], "source": [ - "[a.unique_id for a in m.space.get_neighbors_within_distance(agent, 600000)]" + "class GeoSIR(mesa.Model):\n", + " \"\"\"Model class for a simplistic infection model.\"\"\"\n", + "\n", + " # Geographical parameters for desired map\n", + " geojson_regions = \"data/TorontoNeighbourhoods.geojson\"\n", + " unique_id = \"HOODNUM\"\n", + "\n", + " def __init__(\n", + " self, pop_size=30, mobility_range=500, init_infection=0.2, exposure_dist=500, max_infection_risk=0.2,\n", + " max_recovery_time=5\n", + " ):\n", + " #Scheduler\n", + " self.schedule = mesa.time.RandomActivationByType(self)\n", + " #Space\n", + " self.space = mg.GeoSpace(warn_crs_conversion=False)\n", + " # Data Collection\n", + " self.counts = None #added\n", + " self.reset_counts() #added\n", + " \n", + " # SIR model parameters\n", + " self.pop_size = pop_size\n", + " self.mobility_range = mobility_range\n", + " self.initial_infection = init_infection\n", + " self.exposure_distance = exposure_dist\n", + " self.infection_risk = max_infection_risk\n", + " self.recovery_rate = max_recovery_time\n", + " self.running = True #added\n", + " #added\n", + " self.datacollector = mesa.DataCollector(\n", + " {\n", + " \"infected\": get_infected_count,\n", + " \"susceptible\": get_susceptible_count,\n", + " \"recovered\": get_recovered_count,\n", + " \"dead\": get_dead_count,\n", + " \"safe\": get_safe_count, \n", + " \"hotspot\": get_hotspot_count\n", + " }\n", + " )\n", + " \n", + " # Set up the Neighbourhood patches for every region in file\n", + " ac = mg.AgentCreator(NeighbourhoodAgent, model=self)\n", + " neighbourhood_agents = ac.from_file(\n", + " self.geojson_regions, unique_id=self.unique_id\n", + " )\n", + " \n", + " #Add neighbourhood agents to space\n", + " self.space.add_agents(neighbourhood_agents)\n", + " \n", + " #Add neighbourhood agents to scheduler \n", + " for agent in neighbourhood_agents:\n", + " self.schedule.add(agent)\n", + " \n", + " \n", + " # Generate random location, add agent to grid and scheduler\n", + " for i in range(pop_size):\n", + " #assess if they are infected\n", + " if self.random.random() < self.initial_infection: \n", + " agent_type = \"infected\"\n", + " else: \n", + " agent_type = \"susceptible\"\n", + " #determine movement range\n", + " mobility_range = self.random.randint(0,self.mobility_range) \n", + " #determine agent recovery rate\n", + " recover = self.random.randint(1,self.recovery_rate)\n", + " #determine agents infection risk\n", + " infection_risk = self.random.uniform(0,self.infection_risk)\n", + " #determine agent death probability \n", + " death_risk= self.random.uniform(0,0.05)\n", + "\n", + " # Generate PersonAgent population\n", + " unique_person = mg.AgentCreator(\n", + " PersonAgent,\n", + " model=self,\n", + " crs=self.space.crs,\n", + " agent_kwargs={\"agent_type\": agent_type, \n", + " \"mobility_range\":mobility_range,\n", + " \"recovery_rate\":recover,\n", + " \"infection_risk\": infection_risk,\n", + " \"death_risk\": death_risk\n", + " }\n", + " )\n", + " \n", + " \n", + " x_home, y_home = self.find_home(neighbourhood_agents)\n", + " \n", + " this_person = unique_person.create_agent(\n", + " Point(x_home, y_home), \"P\" + str(i), \n", + " )\n", + " self.space.add_agents(this_person)\n", + " self.schedule.add(this_person)\n", + " \n", + " \n", + " def find_home(self, neighbourhood_agents): \n", + " \"\"\" Find start location of agent \"\"\"\n", + "\n", + " #identify location\n", + " this_neighbourhood = self.random.randint(\n", + " 0, len(neighbourhood_agents) - 1\n", + " ) # Region where agent starts\n", + " center_x, center_y = neighbourhood_agents[\n", + " this_neighbourhood\n", + " ].geometry.centroid.coords.xy\n", + " this_bounds = neighbourhood_agents[this_neighbourhood].geometry.bounds\n", + " spread_x = int(\n", + " this_bounds[2] - this_bounds[0]\n", + " ) # Heuristic for agent spread in region\n", + " spread_y = int(this_bounds[3] - this_bounds[1])\n", + " this_x = center_x[0] + self.random.randint(0, spread_x) - spread_x / 2\n", + " this_y = center_y[0] + self.random.randint(0, spread_y) - spread_y / 2\n", + "\n", + " return this_x, this_y\n", + " \n", + " #added\n", + " def reset_counts(self):\n", + " self.counts = {\n", + " \"susceptible\": 0,\n", + " \"infected\": 0,\n", + " \"recovered\": 0,\n", + " \"dead\": 0,\n", + " \"safe\": 0,\n", + " \"hotspot\": 0,\n", + " }\n", + " \n", + " \n", + " def step(self):\n", + " \"\"\"Run one step of the model.\"\"\"\n", + " \n", + " self.reset_counts() #added\n", + " self.schedule.step()\n", + " self.datacollector.collect(self) #added\n", + "\n", + " # Run until no one is infected\n", + " if self.counts[\"infected\"] == 0 :\n", + " self.running = False\n" ] }, { "cell_type": "markdown", - "id": "78744f63", + "id": "ae5bf0be-e748-4850-9cec-ecbb5a0c2201", + "metadata": { + "explanatory": true + }, + "source": [ + "\n", + "To test our code we will run the model through 5 steps and then call the model dataframe via data collector with `get_model_vars_dataframe()`. This will show a Pandas DataFrame." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "027a8065-13d7-4bc2-96ba-9c556ce16b3c", + "metadata": { + "has_explanation": true + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
infectedsusceptiblerecovereddeadsafehotspot
08220032
15223041
22226041
32253050
41281050
\n", + "
" + ], + "text/plain": [ + " infected susceptible recovered dead safe hotspot\n", + "0 8 22 0 0 3 2\n", + "1 5 22 3 0 4 1\n", + "2 2 22 6 0 4 1\n", + "3 2 25 3 0 5 0\n", + "4 1 28 1 0 5 0" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model = GeoSIR()\n", + "for i in range(5): \n", + " model.step()\n", + "\n", + "model.datacollector.get_model_vars_dataframe()" + ] + }, + { + "cell_type": "markdown", + "id": "f299f22b-9115-48a4-8277-ede19628707c", "metadata": {}, "source": [ - "The unit for the distance depends on the coordinate reference system (CRS) of the GeoSpace. Since we did not specify the CRS, Mesa-Geo defaults to the 'Web Mercator' projection (in meters). If you want to do some serious measurements you should always set an appropriate CRS, since the accuracy of Web Mercator declines with distance from the equator. We can achieve this by initializing the AgentCreator and the GeoSpace with the `crs` keyword `crs=\"epsg:2163\"`. Mesa-Geo then transforms all coordinates from the GeoJSON geographic coordinates into the set crs." + "## Part 3 - Add Interface" ] }, { "cell_type": "markdown", - "id": "d56743c5", + "id": "1035cc55-0169-4096-8b63-d99b8259c980", "metadata": {}, "source": [ - "## Going further\n", + "Adding the interface requires three steps: \n", + "\n", + "1. Define the agent portrayal\n", + "2. Set the sliders for the model parameters\n", + "3. Call the model through the Mesa-Geo visualization model" + ] + }, + { + "cell_type": "markdown", + "id": "b5924dd0-38e6-4b16-911e-4bd93d80976e", + "metadata": { + "explanatory": true + }, + "source": [ + "\n", + "Visualizing agents is done through a function that is is passed in as a parameter. By default agents they are Point geometries are rendered as circles. However, Mesa uses [ipyleaflet](https://ipyleaflet.readthedocs.io/en/latest/layers/marker.html) Users can pass through any Point geomtery for their Agent (i.e. Marker, Circle, Icon, AwesomeIcon). To show this we will use different colors for the PersonAgent base don infection status and if they die, we will use the [Font Awesome Icons](https://fontawesome.com/v4/) and represent them with an x, in the traditional ipyleaflet marker. \n", + "\n", + "We will also change the color of the NeighbourhoodAgent based whether or not it is a hotspot\n", + "\n", + "Next we will build Sliders for each of our input parameters. These use the [Solara's input approach](https://solara.dev/documentation/components/input/slider). This is stored in a dictionary of dictionaries that is then passed through in the model instantiation. \n", + "\n", + "If you want the model to fill the entire screen you can hit the expand button in the upper right. " + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "4d19cb45-3499-47e2-b5b8-2096bc0fc268", + "metadata": { + "editable": true, + "has_explanation": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def SIR_draw(agent):\n", + " \"\"\"\n", + " Portrayal Method for canvas\n", + " \"\"\"\n", + " portrayal = {}\n", + " if isinstance(agent, PersonAgent): \n", + " if agent.atype == \"susceptible\":\n", + " portrayal[\"color\"] = \"Green\"\n", + " elif agent.atype == \"infected\":\n", + " portrayal[\"color\"] = \"Red\"\n", + " elif agent.atype == \"recovered\":\n", + " portrayal[\"color\"] = \"Blue\"\n", + " else: \n", + " portrayal[\"marker_type\"] = \"AwesomeIcon\"\n", + " portrayal[\"name\"] = \"times\"\n", + " portrayal[\"icon_properties\"] = {\n", + " \"marker_color\": 'black',\n", + " \"icon_color\":'white'}\n", + " \n", + " if isinstance(agent, NeighbourhoodAgent):\n", + " if agent.atype == \"hotspot\":\n", + " portrayal[\"color\"] = \"Red\"\n", + " else: \n", + " portrayal[\"color\"] = \"Green\"\n", + " \n", + " return portrayal\n", "\n", - "To get a deeper understanding of Mesa-Geo you should check out the [GeoSchelling](https://github.com/projectmesa/mesa-examples/tree/main/gis/geo_schelling) example. It implements a Leaflet visualization which is similar to use as the CanvasGridVisualization of Mesa.\n", "\n", - "To add further functionality, I need feedback on which functionality is desired by users. Please post a message at [Mesa-Geo discussions](https://github.com/projectmesa/mesa-geo/discussions) or open an [issue](https://github.com/projectmesa/mesa-geo/issues) if you have any ideas or recommendations." + "model_params = {\n", + " \"pop_size\": {\n", + " \"type\": \"SliderInt\",\n", + " \"value\": 80,\n", + " \"label\": \"Population Size\",\n", + " \"min\": 0,\n", + " \"max\": 100, \n", + " \"step\": 1,\n", + " },\n", + " \"mobility_range\": {\n", + " \"type\": \"SliderInt\",\n", + " \"value\": 500,\n", + " \"label\": \"Max Possible Agent Movement\",\n", + " \"min\": 100,\n", + " \"max\": 1000, \n", + " \"step\": 50,\n", + " },\n", + " \"init_infection\": {\n", + " \"type\": \"SliderFloat\",\n", + " \"value\": 0.4,\n", + " \"label\": \"Initial Infection\",\n", + " \"min\": 0.0,\n", + " \"max\": 1.0,\n", + " \"step\": 0.1,\n", + " },\n", + " \"exposure_dist\": {\n", + " \"type\": \"SliderInt\",\n", + " \"value\": 800,\n", + " \"label\": \"Exposure Distance\",\n", + " \"min\": 100,\n", + " \"max\": 1000, \n", + " \"step\": 50,\n", + " },\n", + " \"max_infection_risk\": {\n", + " \"type\": \"SliderFloat\",\n", + " \"value\": 0.7,\n", + " \"label\": \"Maximum Infection Risk\",\n", + " \"min\": 0.0,\n", + " \"max\": 1.0,\n", + " \"step\": 0.1\n", + " },\n", + " \"max_recovery_time\": {\n", + " \"type\": \"SliderInt\",\n", + " \"value\": 7,\n", + " \"label\": \"Maximum Number of Steps to Recover\",\n", + " \"min\": 1,\n", + " \"max\": 10, \n", + " \"step\": 1, \n", + " }}" ] + }, + { + "cell_type": "markdown", + "id": "ee5f4df8-f8af-40a1-a469-843b4c043866", + "metadata": { + "explanatory": true + }, + "source": [ + "To create the model with the interface we use [Mesa's GeoJupyterViz module](https://github.com/projectmesa/mesa-geo/tree/main/mesa_geo/visualization). First we pass in the model class and next the parameters. We then switch to key word arguments. First measures, in this case of list of lists, where the first list will be a chart of the PersonAgent statuses and the second chart will be the NeighbourhoodAgent statuses. We also pass in a name, our agent portrayal function a zoom level and in this case set the scrool wheel zoom to false. " + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "daf1d216-0001-45c6-9527-450036d42764", + "metadata": { + "editable": true, + "has_explanation": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\thoma\\Documents\\GitHub\\dev\\mesa-geo\\mesa_geo\\geoagent.py:38: FutureWarning: The Mesa Model class was not initialized. In the future, you need to explicitly initialize the Model by calling super().__init__() on initialization.\n", + " Agent.__init__(self, unique_id, model)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Red', location=[43.70727983381221, -79.40529846785549], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n", + "Circle(color='Green', location=[43.732483971554416, -79.39618371899114], options=['color', 'dash_array', 'fill', 'fill_color', 'fill_opacity', 'line_cap', 'line_join', 'opacity', 'pointer_events', 'radius', 'stroke', 'weight'], radius=5)\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "d586bb96829a4404bc49f17255445cff", + "version_major": 2, + "version_minor": 0 + }, + "text/html": [ + "Cannot show widget. You probably want to rerun the code cell above (Click in the code cell, and press Shift+Enter +)." + ], + "text/plain": [ + "Cannot show ipywidgets in text" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "page = mgv.GeoJupyterViz(\n", + " GeoSIR,\n", + " model_params,\n", + " measures= [[\"infected\", \"susceptible\", \"recovered\", \"dead\"], [\"safe\", \"hotspot\"]],\n", + " name=\"GeoSIR\",\n", + " agent_portrayal=SIR_draw,\n", + " zoom=12,\n", + " scroll_wheel_zoom=False\n", + ")\n", + "# This is required to render the visualization in the Jupyter notebook\n", + "page" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f789c49b-ae8c-49eb-8bcf-8231ccb33454", + "metadata": { + "has_explanation": false + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "da907e19-46de-4faa-8ba9-193c6530461c", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Xeus-Python (Python 3.10)", "language": "python", - "name": "python3" + "name": "mesa_geo" }, "language_info": { "codemirror_mode": { @@ -231,19 +1924,6 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.3" - }, - "toc": { - "base_numbering": 1, - "nav_menu": {}, - "number_sections": false, - "sideBar": true, - "skip_h1_title": false, - "title_cell": "Table of Contents", - "title_sidebar": "Contents", - "toc_cell": false, - "toc_position": {}, - "toc_section_display": true, - "toc_window_display": true } }, "nbformat": 4, diff --git a/mesa_geo/visualization/geojupyter_viz.py b/mesa_geo/visualization/geojupyter_viz.py index 5002165f..4040a683 100644 --- a/mesa_geo/visualization/geojupyter_viz.py +++ b/mesa_geo/visualization/geojupyter_viz.py @@ -1,5 +1,3 @@ -import sys - import matplotlib.pyplot as plt import mesa.experimental.components.matplotlib as components_matplotlib import solara @@ -30,16 +28,54 @@ def Card( map_drawer, center_default, zoom, + scroll_wheel_zoom, current_step, color, layout_type, ): + """ + + + Parameters + ---------- + model : Mesa Model Object + A pointer to the Mesa Model object this allows the visual to get get + model information, such as scheduler and space. + measures : List + Plots associated with model typically from datacollector that represent + critical information collected from the model. + agent_portrayal : Dictionary + Contains details of how visualization should plot key elements of the + such as agent color etc + map_drawer : Method + Function that generates map from GIS data of model + center_default : List + Latitude and Longitude of where center of map should be located + zoom : Int + Zoom level at which to intialize the map + scroll_wheel_zoom: Boolean + True of False on whether user can zoom on map with mouse scroll wheel + default is True + current_step : Int + Number on which step is the model + color : String + Background color for visual + layout_type : String + Type of layout Map or Measure + + Returns + ------- + main : Solara object + Visualization of model + + """ + with rv.Card( style_=f"background-color: {color}; width: 100%; height: 100%" ) as main: if "Map" in layout_type: rv.CardTitle(children=["Map"]) - leaflet_viz.map(model, map_drawer, zoom, center_default) + leaflet_viz.map(model, map_drawer, zoom, center_default, scroll_wheel_zoom) if "Measure" in layout_type: rv.CardTitle(children=["Measure"]) @@ -65,23 +101,50 @@ def GeoJupyterViz( # parameters for leaflet_viz view=None, zoom=None, + scroll_wheel_zoom=True, tiles=xyz.OpenStreetMap.Mapnik, center_point=None, # Due to projection challenges in calculation allow user to specify center point ): - """Initialize a component to visualize a model. - Args: - model_class: class of the model to instantiate - model_params: parameters for initializing the model - measures: list of callables or data attributes to plot - name: name for display - agent_portrayal: options for rendering agents (dictionary) - space_drawer: method to render the agent space for - the model; default implementation is the `SpaceMatplotlib` component; - simulations with no space to visualize should - specify `space_drawer=False` - play_interval: play interval (default: 150) - center_point: list of center coords """ + + + Parameters + ---------- + model_class : Mesa Model Object + A pointer to the Mesa Model object this allows the visual to get get + model information, such as scheduler and space. + model_params : Dictionary + Parameters of model with key being the paramter and values being the options + measures : List, optional + Plots associated with model typically from datacollector that represent + critical information collected from the model. The default is None. + name : String, optional + Name of simulation to appear on visual. The default is None. + agent_portrayal : Dictionary, optional + Dictionary of how the agent showed appear. The default is None. + play_interval : INT, optional + Rendering interval of model. The default is 150. + # parameters for leaflet_viz + view : List, optional + Bounds of map to be displayed; must be set with zoom. The default is None. + zoom : Int, optional + Zoom level of map on leaflet + scroll_wheel_zoom : Boolean, optional + True of False for whether or not to enable scroll wheel. The default is True. + Recommend False is in jupyter due to multiple scroll whell options + tiles : Data source for GIS data, optional + Data Source for GIS map data. The default is xyz.OpenStreetMap.Mapnik. + # Due to projection challenges in calculation allow user to specify + center_point : List, optional + Option to pass in center coordinates of map The default is None.. The default is None. + + + Returns + ------- + Provides information to Card to render model + + """ + if name is None: name = model_class.__name__ @@ -118,6 +181,7 @@ def handle_change_model_params(name: str, value: any): view=view, zoom=zoom, tiles=tiles, + scroll_wheel_zoom=scroll_wheel_zoom, ) layers = map_drawer.render(model) @@ -126,86 +190,47 @@ def handle_change_model_params(name: str, value: any): center_default = center_point else: bounds = layers["layers"]["total_bounds"] - center_default = list((bounds[2:] + bounds[:2]) / 2) + center_default = [ + (bounds[0][0] + bounds[1][0]) / 2, + (bounds[0][1] + bounds[1][1]) / 2, + ] + + # Build base data structure for layout + layout_types = [{"Map": "default"}] + + if measures: + layout_types += [{"Measure": elem} for elem in range(len(measures))] - def render_in_jupyter(): - # TODO: Build API to allow users to set rows and columns - # call in property of model layers geospace line; use 1 column to prevent map overlap + grid_layout_initial = jv.make_initial_grid_layout(layout_types=layout_types) + grid_layout, set_grid_layout = solara.use_state(grid_layout_initial) - with solara.Row( - justify="space-between", style={"flex-grow": "1"} - ) and solara.GridFixed(columns=2): + with solara.Sidebar(): + with solara.Card("Controls", margin=1, elevation=2): jv.UserInputs(user_params, on_change=handle_change_model_params) jv.ModelController(model, play_interval, current_step, reset_counter) - solara.Markdown(md_text=f"###Step - {current_step}") - - # Builds Solara component of map - leaflet_viz.map_jupyter(model, map_drawer, zoom, center_default) - - # Place measurement in separate row - with solara.Row( - justify="space-between", - style={"flex-grow": "1"}, - ): - # 5. Plots - for measure in measures: - if callable(measure): - # Is a custom object - measure(model) - else: - components_matplotlib.PlotMatplotlib( - model, measure, dependencies=[current_step.value] - ) - - def render_in_browser(): - # determine center point - if center_point: - center_default = center_point - else: - bounds = layers["layers"]["total_bounds"] - center_default = list((bounds[2:] + bounds[:2]) / 2) - - # if space drawer is disabled, do not include it - layout_types = [{"Map": "default"}] - - if measures: - layout_types += [{"Measure": elem} for elem in range(len(measures))] - - grid_layout_initial = jv.make_initial_grid_layout(layout_types=layout_types) - grid_layout, set_grid_layout = solara.use_state(grid_layout_initial) - - with solara.Sidebar(): - with solara.Card("Controls", margin=1, elevation=2): - jv.UserInputs(user_params, on_change=handle_change_model_params) - jv.ModelController(model, play_interval, current_step, reset_counter) - with solara.Card("Progress", margin=1, elevation=2): - solara.Markdown(md_text=f"####Step - {current_step}") - - items = [ - Card( - model, - measures, - agent_portrayal, - map_drawer, - center_default, - zoom, - current_step, - color="white", - layout_type=layout_types[i], - ) - for i in range(len(layout_types)) - ] - - solara.GridDraggable( - items=items, - grid_layout=grid_layout, - resizable=True, - draggable=True, - on_grid_layout=set_grid_layout, + with solara.Card("Progress", margin=1, elevation=2): + solara.Markdown(md_text=f"####Step - {current_step}") + + items = [ + Card( + model, + measures, + agent_portrayal, + map_drawer, + center_default, + zoom, + scroll_wheel_zoom, + current_step, + color="white", + layout_type=layout_types[i], ) - - if ("ipykernel" in sys.argv[0]) or ("colab_kernel_launcher.py" in sys.argv[0]): - # When in Jupyter or Google Colab - render_in_jupyter() - else: - render_in_browser() + for i in range(len(layout_types)) + ] + + solara.GridDraggable( + items=items, + grid_layout=grid_layout, + resizable=True, + draggable=True, + on_grid_layout=set_grid_layout, + ) diff --git a/mesa_geo/visualization/leaflet_viz.py b/mesa_geo/visualization/leaflet_viz.py index d86aa3b1..3c46b1a6 100644 --- a/mesa_geo/visualization/leaflet_viz.py +++ b/mesa_geo/visualization/leaflet_viz.py @@ -18,7 +18,7 @@ @solara.component -def map(model, map_drawer, zoom, center_default): +def map(model, map_drawer, zoom, center_default, scroll_wheel_zoom): # render map in browser zoom_map = solara.reactive(zoom) center = solara.reactive(center_default) @@ -29,7 +29,7 @@ def map(model, map_drawer, zoom, center_default): ipyleaflet.Map.element( zoom=zoom_map.value, center=center.value, - scroll_wheel_zoom=True, + scroll_wheel_zoom=scroll_wheel_zoom, layers=[ ipyleaflet.TileLayer.element(url=base_map["url"]), ipyleaflet.GeoJSON.element(data=layers["agents"][0]), @@ -38,28 +38,6 @@ def map(model, map_drawer, zoom, center_default): ) -@solara.component -def map_jupyter(model, map_drawer, zoom, center_default): - zoom_map = solara.reactive(zoom) - center = solara.reactive(center_default) - - base_map = map_drawer.tiles - layers = map_drawer.render(model) - - # prevents overlap of map with measures - with solara.Column(style={"isolation": "isolate"}): - ipyleaflet.Map.element( - zoom=zoom_map.value, - center=center.value, - scroll_wheel_zoom=True, - layers=[ - ipyleaflet.TileLayer.element(url=base_map["url"]), - ipyleaflet.GeoJSON.element(data=layers["agents"][0]), - *layers["agents"][1], - ], - ) - - @dataclass class LeafletViz: """A dataclass defining the portrayal of a GeoAgent in Leaflet map. @@ -89,6 +67,7 @@ def __init__( portrayal_method, view, zoom, + scroll_wheel_zoom, tiles, ): """ @@ -102,8 +81,7 @@ def __init__( :param zoom: The initial zoom level of the map. Must be set together with view. If both view and zoom are None, the map will be centered on the total bounds of the space. Default is None. - :param map_width: The width of the map in pixels. Default is 500. - :param map_height: The height of the map in pixels. Default is 500. + :param scroll_wheel_zoom: Boolean whether not user can scroll on map with mouse wheel :param tiles: An optional tile layer to use. Can be a :class:`RasterWebTile` or a :class:`xyzservices.TileProvider`. Default is `xyzservices.providers.OpenStreetMap.Mapnik`. @@ -217,8 +195,22 @@ def _get_marker(self, location, properties): return ipyleaflet.Circle(location=location, **properties) elif marker == "CircleMarker": return ipyleaflet.CircleMarker(location=location, **properties) - elif marker == "Marker" or marker == "Icon" or marker == "AwesomeIcon": + elif marker == "Marker": return ipyleaflet.Marker(location=location, **properties) + elif marker == "Icon": + icon_url = properties["icon_url"] + icon_size = properties.get("icon_size", [20, 20]) + icon_properties = properties.get("icon_properties", {}) + icon = ipyleaflet.Icon( + icon_url=icon_url, icon_size=icon_size, **icon_properties + ) + return ipyleaflet.Marker(location=location, icon=icon, **properties) + elif marker == "AwesomeIcon": + name = properties["name"] + icon_properties = properties.get("icon_properties", {}) + icon = ipyleaflet.AwesomeIcon(name=name, **icon_properties) + return ipyleaflet.Marker(location=location, icon=icon, **properties) + else: raise ValueError( f"Unsupported marker type:{marker}", @@ -245,16 +237,16 @@ def _render_agents(self, model): point_markers.append(self._get_marker(location, properties)) else: agent_portrayal.style = properties - agent_portrayal = dataclasses.asdict( - agent_portrayal, - dict_factory=lambda x: {k: v for (k, v) in x if v is not None}, - ) - - feature_collection["features"].append( - { - "type": "Feature", - "geometry": mapping(transformed_geometry), - "properties": agent_portrayal, - } - ) + agent_portrayal = dataclasses.asdict( + agent_portrayal, + dict_factory=lambda x: {k: v for (k, v) in x if v is not None}, + ) + + feature_collection["features"].append( + { + "type": "Feature", + "geometry": mapping(transformed_geometry), + "properties": agent_portrayal, + } + ) return [feature_collection, point_markers] diff --git a/tests/test_GeoJupyterViz.py b/tests/test_GeoJupyterViz.py index eb155ab1..1a646475 100644 --- a/tests/test_GeoJupyterViz.py +++ b/tests/test_GeoJupyterViz.py @@ -21,9 +21,10 @@ def test_card_function( model = MagicMock() measures = {"Measure1": lambda x: x} agent_portrayal = MagicMock() - map_drawer = MagicMock() - center_default = [0, 0] - zoom = 10 + map_drawer = (MagicMock(),) + zoom = (10,) + scroll_wheel_zoom = (True,) + center_default = ([0, 0],) current_step = MagicMock() current_step.value = 0 color = "white" @@ -39,6 +40,7 @@ def test_card_function( map_drawer, center_default, zoom, + scroll_wheel_zoom, current_step, color, layout_type, @@ -46,7 +48,9 @@ def test_card_function( mock_rv_card.assert_called_once() mock_CardTitle.assert_any_call(children=["Map"]) - mock_map.assert_called_once_with(model, map_drawer, zoom, center_default) + mock_map.assert_called_once_with( + model, map_drawer, zoom, center_default, scroll_wheel_zoom + ) # mock_PlotMatplotlib.assert_called_once() @patch("mesa_geo.visualization.geojupyter_viz.solara.GridDraggable") diff --git a/tests/test_MapModule.py b/tests/test_MapModule.py index 6700c35c..cc3010d3 100644 --- a/tests/test_MapModule.py +++ b/tests/test_MapModule.py @@ -47,6 +47,7 @@ def test_render_point_agents(self): portrayal_method=lambda x: {"color": "Green"}, view=None, zoom=3, + scroll_wheel_zoom=True, tiles=xyz.OpenStreetMap.Mapnik, ) self.model.space.add_agents(self.point_agents) @@ -60,6 +61,7 @@ def test_render_point_agents(self): }, view=None, zoom=3, + scroll_wheel_zoom=True, tiles=xyz.OpenStreetMap.Mapnik, ) self.model.space.add_agents(self.point_agents) @@ -69,9 +71,14 @@ def test_render_point_agents(self): # test Marker option map_module = mgv.leaflet_viz.MapModule( - portrayal_method=lambda x: {"marker_type": "Icon", "color": "Green"}, + portrayal_method=lambda x: { + "marker_type": "AwesomeIcon", + "name": "bus", + "color": "Green", + }, view=None, zoom=3, + scroll_wheel_zoom=True, tiles=xyz.OpenStreetMap.Mapnik, ) self.model.space.add_agents(self.point_agents) @@ -86,23 +93,16 @@ def test_render_point_agents(self): }, view=None, zoom=3, + scroll_wheel_zoom=False, tiles=xyz.OpenStreetMap.Mapnik, ) self.model.space.add_agents(self.point_agents) + print(map_module.render(self.model).get("agents")[0]) self.assertDictEqual( map_module.render(self.model).get("agents")[0], { "type": "FeatureCollection", - "features": [ - { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": (1.0, 1.0)}, - "properties": { - "popupProperties": "popupMsg", - }, - } - ] - * len(self.point_agents), + "features": [] * len(self.point_agents), }, ) @@ -111,6 +111,7 @@ def test_render_point_agents(self): portrayal_method=lambda x: {"marker_type": "Hexagon", "color": "Green"}, view=None, zoom=3, + scroll_wheel_zoom=True, tiles=xyz.OpenStreetMap.Mapnik, ) self.model.space.add_agents(self.point_agents) @@ -122,6 +123,7 @@ def test_render_line_agents(self): portrayal_method=lambda x: {"color": "#3388ff", "weight": 7}, view=None, zoom=3, + scroll_wheel_zoom=False, tiles=xyz.OpenStreetMap.Mapnik, ) self.model.space.add_agents(self.line_agents) @@ -151,6 +153,7 @@ def test_render_line_agents(self): }, view=None, zoom=3, + scroll_wheel_zoom=True, tiles=xyz.OpenStreetMap.Mapnik, ) self.model.space.add_agents(self.line_agents) @@ -182,6 +185,7 @@ def test_render_polygon_agents(self): portrayal_method=lambda x: {"fillColor": "#3388ff", "fillOpacity": 0.7}, view=None, zoom=3, + scroll_wheel_zoom=False, tiles=xyz.OpenStreetMap.Mapnik, ) self.model.space.add_agents(self.polygon_agents) @@ -215,6 +219,7 @@ def test_render_polygon_agents(self): }, view=None, zoom=3, + scroll_wheel_zoom=True, tiles=xyz.OpenStreetMap.Mapnik, ) self.model.space.add_agents(self.polygon_agents) @@ -246,6 +251,7 @@ def test_render_raster_layers(self): portrayal_method=lambda x: (255, 255, 255, 0.5), view=None, zoom=3, + scroll_wheel_zoom=True, tiles=xyz.OpenStreetMap.Mapnik, ) self.model.space.add_layer(self.raster_layer)