Louppian commited on
Commit
6b09cbd
ยท
verified ยท
1 Parent(s): 95e2e49

Upload folder using huggingface_hub

Browse files
.gitattributes CHANGED
@@ -33,3 +33,8 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ Data/inha_boundary_map.png filter=lfs diff=lfs merge=lfs -text
37
+ Data/inha_boundary_polygon.png filter=lfs diff=lfs merge=lfs -text
38
+ Data/inha_boundary_satellite.png filter=lfs diff=lfs merge=lfs -text
39
+ ๋ฐ์ดํ„ฐ[[:space:]]์ˆ˜์ง‘/inha_boundary_with_points.png filter=lfs diff=lfs merge=lfs -text
40
+ ๋ชจ์ˆ˜ํƒ์ •๋‹จ_์ œ์•ˆ์„œ.pdf filter=lfs diff=lfs merge=lfs -text
.gradio/certificate.pem ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
3
+ TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
4
+ cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
5
+ WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
6
+ ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
7
+ MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
8
+ h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
9
+ 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
10
+ A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
11
+ T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
12
+ B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
13
+ B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
14
+ KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
15
+ OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
16
+ jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
17
+ qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
18
+ rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
19
+ HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
20
+ hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
21
+ ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
22
+ 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
23
+ NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
24
+ ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
25
+ TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
26
+ jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
27
+ oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
28
+ 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
29
+ mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
30
+ emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
31
+ -----END CERTIFICATE-----
Data/inha_boundary.cpg ADDED
@@ -0,0 +1 @@
 
 
1
+ UTF-8
Data/inha_boundary.dbf ADDED
Binary file (819 Bytes). View file
 
Data/inha_boundary.geojson ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "type": "FeatureCollection",
3
+ "features": [
4
+ {
5
+ "type": "Feature",
6
+ "geometry": {
7
+ "type": "Polygon",
8
+ "coordinates": [
9
+ [
10
+ [
11
+ 126.6504913,
12
+ 37.4488536
13
+ ],
14
+ [
15
+ 126.6502018,
16
+ 37.4490381
17
+ ],
18
+ [
19
+ 126.6501274,
20
+ 37.4490101
21
+ ],
22
+ [
23
+ 126.650006,
24
+ 37.449035
25
+ ],
26
+ [
27
+ 126.6499295,
28
+ 37.4493095
29
+ ],
30
+ [
31
+ 126.6497967,
32
+ 37.4495766
33
+ ],
34
+ [
35
+ 126.6496642,
36
+ 37.4498464
37
+ ],
38
+ [
39
+ 126.6493324,
40
+ 37.4504076
41
+ ],
42
+ [
43
+ 126.6490067,
44
+ 37.4509487
45
+ ],
46
+ [
47
+ 126.649781,
48
+ 37.4514003
49
+ ],
50
+ [
51
+ 126.6495914,
52
+ 37.4517312
53
+ ],
54
+ [
55
+ 126.6501326,
56
+ 37.45175
57
+ ],
58
+ [
59
+ 126.6505999,
60
+ 37.4517726
61
+ ],
62
+ [
63
+ 126.6510982,
64
+ 37.4518266
65
+ ],
66
+ [
67
+ 126.6512838,
68
+ 37.4518467
69
+ ],
70
+ [
71
+ 126.6517828,
72
+ 37.4519342
73
+ ],
74
+ [
75
+ 126.6520048,
76
+ 37.4519731
77
+ ],
78
+ [
79
+ 126.6520885,
80
+ 37.4519878
81
+ ],
82
+ [
83
+ 126.6524237,
84
+ 37.4520465
85
+ ],
86
+ [
87
+ 126.6525693,
88
+ 37.452072
89
+ ],
90
+ [
91
+ 126.6528855,
92
+ 37.4521371
93
+ ],
94
+ [
95
+ 126.6530797,
96
+ 37.4521045
97
+ ],
98
+ [
99
+ 126.6536373,
100
+ 37.4518765
101
+ ],
102
+ [
103
+ 126.6544021,
104
+ 37.4516675
105
+ ],
106
+ [
107
+ 126.6564271,
108
+ 37.4511471
109
+ ],
110
+ [
111
+ 126.6578465,
112
+ 37.4507776
113
+ ],
114
+ [
115
+ 126.6569217,
116
+ 37.4492734
117
+ ],
118
+ [
119
+ 126.6553859,
120
+ 37.4466845
121
+ ],
122
+ [
123
+ 126.6547144,
124
+ 37.4468884
125
+ ],
126
+ [
127
+ 126.6545294,
128
+ 37.4469713
129
+ ],
130
+ [
131
+ 126.6544155,
132
+ 37.4470167
133
+ ],
134
+ [
135
+ 126.653386,
136
+ 37.4474272
137
+ ],
138
+ [
139
+ 126.6527874,
140
+ 37.4476634
141
+ ],
142
+ [
143
+ 126.6507639,
144
+ 37.4484888
145
+ ],
146
+ [
147
+ 126.6505808,
148
+ 37.4485626
149
+ ],
150
+ [
151
+ 126.6505271,
152
+ 37.4487565
153
+ ],
154
+ [
155
+ 126.6504913,
156
+ 37.4488536
157
+ ]
158
+ ]
159
+ ]
160
+ },
161
+ "properties": {
162
+ "amenity": "university",
163
+ "education": "university",
164
+ "name": "์ธํ•˜๋Œ€ํ•™๊ต",
165
+ "name:en": "Inha University",
166
+ "name:ja": "ไป่ทๅคงๅญฆๆ ก",
167
+ "name:zh": "ไป่ทๅคงๅญฆ",
168
+ "wikidata": "Q488851"
169
+ }
170
+ }
171
+ ]
172
+ }
Data/inha_boundary.prj ADDED
@@ -0,0 +1 @@
 
 
1
+ GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]]
Data/inha_boundary.shp ADDED
Binary file (748 Bytes). View file
 
Data/inha_boundary.shx ADDED
Binary file (108 Bytes). View file
 
Data/inha_boundary_map.png ADDED

Git LFS Details

  • SHA256: 79c05fa581eddfb2d522b1f399d455b8379c1af415046cd06254fa5ad73c5c03
  • Pointer size: 132 Bytes
  • Size of remote file: 2.35 MB
Data/inha_boundary_polygon.png ADDED

Git LFS Details

  • SHA256: 67bf935af41880b18a2617b7037d3725abc6a850559151fdc150d8757dbcbe51
  • Pointer size: 131 Bytes
  • Size of remote file: 320 kB
Data/inha_boundary_satellite.png ADDED

Git LFS Details

  • SHA256: a522723ed855ec9ff3b65e55f76725a3509ef04386cb8e22824caa1439b6d503
  • Pointer size: 133 Bytes
  • Size of remote file: 15.6 MB
Data/zones.geojson ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {"type":"FeatureCollection","features":[{"type":"Feature","properties":{},
2
+ "geometry":{"type":"Polygon","coordinates":[[[126.653513,37.450119],[126.6545,37.451635],[126.652805,37.452087],[126.649843,37.451652],[126.653513,37.450119]]]}},{"type":"Feature","properties":{},
3
+ "geometry":{"type":"Polygon","coordinates":[[[126.653523,37.450059],[126.65009,37.451431],[126.649232,37.45092],[126.650208,37.449157],[126.650594,37.448807],[126.650659,37.44856],[126.652247,37.447921],[126.653523,37.450059]]]}},{"type":"Feature","properties":{},
4
+ "geometry":{"type":"Polygon","coordinates":[[[126.653588,37.450119],[126.654553,37.451618],[126.656442,37.451116],[126.655476,37.449369],[126.653588,37.450119]]]}},{"type":"Feature","properties":{},
5
+ "geometry":{"type":"Polygon","coordinates":[[[126.65554,37.449327],[126.656474,37.451073],[126.657826,37.450766],[126.656656,37.448875],[126.65554,37.449327]]]}},{"type":"Feature","properties":{},
6
+ "geometry":{"type":"Polygon","coordinates":[[[126.655465,37.449284],[126.653534,37.450021],[126.6523,37.447904],[126.65436,37.447053],[126.655465,37.449284]]]}},{"type":"Feature","properties":{},
7
+ "geometry":{"type":"Polygon","coordinates":[[[126.655519,37.449276],[126.654371,37.447019],[126.655374,37.446703],[126.656662,37.448863],[126.655519,37.449276]]]}}]}
Data/zones_์ตœ์ข….geojson ADDED
@@ -0,0 +1 @@
 
 
1
+ {"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[126.649822,37.451397],[126.65031,37.451388],[126.653615,37.450085],[126.65215,37.447926],[126.650605,37.44859],[126.650509,37.448871],[126.650208,37.449071],[126.650047,37.449059],[126.64905,37.450941],[126.649822,37.451397]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[126.653631,37.450081],[126.65215,37.447926],[126.654484,37.447002],[126.655846,37.449184],[126.653631,37.450081]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[126.654478,37.446985],[126.655852,37.449191],[126.656678,37.448875],[126.655374,37.446695],[126.654478,37.446985]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[126.656688,37.448875],[126.655401,37.449382],[126.655433,37.449676],[126.656442,37.451145],[126.657842,37.450775],[126.656688,37.448875]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[126.655406,37.449378],[126.653529,37.450145],[126.654623,37.45161],[126.656447,37.451145],[126.655417,37.449702],[126.655406,37.449378]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[126.653513,37.45014],[126.650316,37.451409],[126.649806,37.451409],[126.649624,37.451707],[126.650686,37.451763],[126.65288,37.452104],[126.654607,37.451601],[126.653513,37.45014]]]}}]}
Data/์ธก์ •๊ฒฐ๊ณผ_์ˆ˜์ •์ค‘.csv ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ lat,lon,dB
2
+ 37.4516638,126.6527864,2.0
3
+ 37.4517164,126.6525593,30.0
4
+ 37.4512905,126.653364,3.0
5
+ 37.4514279,126.652793,4.0
6
+ 37.4511085,126.6535225,5.0
7
+ 37.4510701,126.6531148,6.0
8
+ 37.4513171,126.652632,7.0
9
+ 37.4513746,126.6517523,24.0
10
+ 37.4512256,126.6514948,21.0
11
+ 37.4510425,126.6515538,22.0
12
+ 37.4510595,126.6526267,8.0
13
+ 37.4505606,126.6533733,14.0
14
+ 37.4506117,126.6531748,25.0
15
+ 37.4514975,126.6537273,28.0
16
+ 37.4509056,126.6523755,26.0
17
+ 37.450863,126.6535503,11.0
18
+ 37.4517914,126.6530514,27.0
19
+ 37.4514336,126.6522414,31.0
20
+ 37.451127,126.6513348,23.0
21
+ 37.4505351,126.6524077,18.0
22
+ 37.4507437,126.6524077,16.0
23
+ 37.4503988,126.6532875,19.0
24
+ 37.4510631,126.6536844,10.0
25
+ 37.4515699,126.6534377,29.0
26
+ 37.4506799,126.6527081,17.0
27
+ 37.4512906,126.6521779,32.0
28
+ 37.4509074,126.6521297,15.0
29
+ 37.4510309,126.6528753,9.0
30
+ 37.4512012,126.6539697,13.0
31
+ 37.4507669,126.6537015,12.0
32
+ 37.4509659,126.6549157,70.0
33
+ 37.4508935,126.6552698,71.0
34
+ 37.4508636,126.6558974,69.0
35
+ 37.4511234,126.6544705,68.0
36
+ 37.4513577,126.6545939,66.0
37
+ 37.4505187,126.6540735,67.0
38
+ 37.4505826,126.6550338,72.0
39
+ 37.4506465,126.6546851,73.0
40
+ 37.4498331,126.6546207,90.0
41
+ 37.4505016,126.6553824,78.0
42
+ 37.4503569,126.6549479,74.0
43
+ 37.4500523,126.6554818,79.0
44
+ 37.4498416,126.6553664,80.0
45
+ 37.449931,126.6551249,84.0
46
+ 37.4501013,126.6545617,86.0
47
+ 37.4500119,126.6549318,83.0
48
+ 37.450259,126.6542881,81.0
49
+ 37.4502206,126.6544383,82.0
50
+ 37.4501525,126.654213,87.0
51
+ 37.4503654,126.655495,77.0
52
+ 37.4497777,126.6547173,91.0
53
+ 37.4505655,126.655243,76.0
54
+ 37.4505698,126.6506188,41.0
55
+ 37.4503398,126.6505276,40.0
56
+ 37.4506251,126.6509514,42.0
57
+ 37.4504676,126.6517346,34.0
58
+ 37.4506252,126.6513859,33.0
59
+ 37.4499438,126.6512143,38.0
60
+ 37.4499054,126.6506027,39.0
61
+ 37.4500971,126.651917,37.0
62
+ 37.4504548,126.6550123,75.0
63
+ 37.4501865,126.6517293,36.0
64
+ 37.4502887,126.6514664,35.0
65
+ 37.4499437,126.6552644,85.0
66
+ 37.4495805,126.6526316,46.0
67
+ 37.4492398,126.6524492,48.0
68
+ 37.4497977,126.6530554,45.0
69
+ 37.4500532,126.6527818,43.0
70
+ 37.4499255,126.6529267,44.0
71
+ 37.4489332,126.6520201,49.0
72
+ 37.4489204,126.6522293,50.0
73
+ 37.449474,126.6528355,47.0
74
+ 37.4494917,126.6541855,57.0
75
+ 37.4499771,126.65462,88.0
76
+ 37.4497514,126.6551403,89.0
77
+ 37.4486569,126.6536919,53.0
78
+ 37.4489806,126.6539548,54.0
79
+ 37.4496024,126.654502,58.0
80
+ 37.4497003,126.6544966,60.0
81
+ 37.4499814,126.6539816,59.0
82
+ 37.4495939,126.654797,61.0
83
+ 37.4495172,126.6545878,56.0
84
+ 37.4493171,126.6552154,62.0
85
+ 37.4491893,126.6552101,63.0
86
+ 37.4490317,126.6551242,65.0
87
+ 37.4491978,126.655076,64.0
88
+ 37.4488827,126.6542498,55.0
89
+ 37.4479436,126.6529785,51.0
90
+ 37.4503967,126.6529463,20.0
91
+ 37.4486122,126.6533164,52.0
92
+ 37.448078,126.6556807,37.0
93
+ 37.4483203,126.6558354,41.0
94
+ 37.4483203,126.655504,47.0
95
+ 37.44746,126.655224,52.0
96
+ 37.4474539,126.6555703,45.0
97
+ 37.4476721,126.6550472,55.0
98
+ 37.4477023,126.655364,49.0
99
+ 37.4477751,126.6551283,56.0
100
+ 37.4488898,126.6557323,53.0
101
+ 37.4490655,126.6560712,55.0
102
+ 37.4489565,126.6559828,54.0
103
+ 37.4492533,126.656108,59.0
104
+ 37.4494472,126.6563216,63.0
105
+ 37.4497198,126.6561375,58.0
106
+ 37.4499379,126.656248,56.0
107
+ 37.4485566,126.6557102,59.0
108
+ 37.4483627,126.6551062,42.0
109
+ 37.4506589,126.6570141,51.0
110
+ 37.4503802,126.656469,58.0
111
+ 37.4505559,126.6571835,48.0
112
+ 37.4492957,126.656329,58.0
113
+ 37.4495078,126.6564248,61.0
Data/์ธก์ •๊ฒฐ๊ณผ_์ตœ์ข….csv ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ lat,lon,dB
2
+ 37.4516638,126.6527864,50.0
3
+ 37.4512905,126.653364,49.0
4
+ 37.4514279,126.652793,48.0
5
+ 37.4511085,126.6535225,49.0
6
+ 37.4510701,126.6531148,48.0
7
+ 37.4513171,126.652632,47.0
8
+ 37.4510595,126.6526267,50.0
9
+ 37.4510309,126.6528753,51.0
10
+ 37.4510631,126.6536844,57.0
11
+ 37.450863,126.6535503,52.0
12
+ 37.4507669,126.6537015,51.0
13
+ 37.4512012,126.6539697,47.0
14
+ 37.4505606,126.6533733,47.0
15
+ 37.4509074,126.6521297,58.0
16
+ 37.4507437,126.6524077,49.0
17
+ 37.4506799,126.6527081,48.0
18
+ 37.4505351,126.6524077,53.0
19
+ 37.4503988,126.6532875,47.0
20
+ 37.4503967,126.6529463,46.0
21
+ 37.4512256,126.6514948,47.0
22
+ 37.4510425,126.6515538,46.0
23
+ 37.451127,126.6513348,46.0
24
+ 37.4513746,126.6517523,42.0
25
+ 37.4506117,126.6531748,44.0
26
+ 37.4509056,126.6523755,42.0
27
+ 37.4517914,126.6530514,55.0
28
+ 37.4514975,126.6537273,42.0
29
+ 37.4515699,126.6534377,41.0
30
+ 37.4517164,126.6525593,39.0
31
+ 37.4514336,126.6522414,50.0
32
+ 37.4512906,126.6521779,42.0
33
+ 37.4506252,126.6513859,53.0
34
+ 37.4504676,126.6517346,45.0
35
+ 37.4502887,126.6514664,50.0
36
+ 37.4501865,126.6517293,47.0
37
+ 37.4500971,126.651917,49.0
38
+ 37.4499438,126.6512143,50.0
39
+ 37.4499054,126.6506027,51.0
40
+ 37.4503398,126.6505276,49.0
41
+ 37.4505698,126.6506188,43.0
42
+ 37.4506251,126.6509514,45.0
43
+ 37.4500532,126.6527818,46.0
44
+ 37.4499255,126.6529267,43.0
45
+ 37.4497977,126.6530554,44.0
46
+ 37.4495805,126.6526316,48.0
47
+ 37.449474,126.6528355,52.0
48
+ 37.4492398,126.6524492,53.0
49
+ 37.4489332,126.6520201,50.0
50
+ 37.4489204,126.6522293,46.0
51
+ 37.4479436,126.6529785,49.0
52
+ 37.4486122,126.6533164,46.0
53
+ 37.4486569,126.6536919,43.0
54
+ 37.4489806,126.6539548,44.0
55
+ 37.4488827,126.6542498,47.0
56
+ 37.4495172,126.6545878,38.0
57
+ 37.4494917,126.6541855,51.0
58
+ 37.4496024,126.654502,52.0
59
+ 37.4499814,126.6539816,52.0
60
+ 37.4497003,126.6544966,46.0
61
+ 37.4495939,126.654797,54.0
62
+ 37.4493171,126.6552154,46.0
63
+ 37.4491893,126.6552101,47.0
64
+ 37.4491978,126.655076,45.0
65
+ 37.4490317,126.6551242,47.0
66
+ 37.4513577,126.6545939,51.0
67
+ 37.4505187,126.6540735,53.0
68
+ 37.4511234,126.6544705,60.0
69
+ 37.4508636,126.6558974,43.0
70
+ 37.4509659,126.6549157,44.0
71
+ 37.4508935,126.6552698,39.0
72
+ 37.4505826,126.6550338,56.0
73
+ 37.4506465,126.6546851,52.0
74
+ 37.4503569,126.6549479,59.0
75
+ 37.4504548,126.6550123,54.0
76
+ 37.4505655,126.655243,42.0
77
+ 37.4503654,126.655495,47.0
78
+ 37.4505016,126.6553824,41.0
79
+ 37.4500523,126.6554818,41.0
80
+ 37.4498416,126.6553664,46.0
81
+ 37.450259,126.6542881,44.0
82
+ 37.4502206,126.6544383,43.0
83
+ 37.4500119,126.6549318,44.0
84
+ 37.449931,126.6551249,51.0
85
+ 37.4499437,126.6552644,46.0
86
+ 37.4501013,126.6545617,50.0
87
+ 37.4501525,126.654213,48.0
88
+ 37.4499771,126.65462,48.0
89
+ 37.4497514,126.6551403,52.0
90
+ 37.4498331,126.6546207,48.0
91
+ 37.4497777,126.6547173,52.0
92
+ 37.448078,126.6556807,37.0
93
+ 37.4483203,126.6558354,41.0
94
+ 37.4483203,126.655504,47.0
95
+ 37.44746,126.655224,52.0
96
+ 37.4474539,126.6555703,45.0
97
+ 37.4476721,126.6550472,55.0
98
+ 37.4477023,126.655364,49.0
99
+ 37.4477751,126.6551283,56.0
100
+ 37.4488898,126.6557323,53.0
101
+ 37.4490655,126.6560712,55.0
102
+ 37.4489565,126.6559828,54.0
103
+ 37.4492533,126.656108,59.0
104
+ 37.4494472,126.6563216,63.0
105
+ 37.4497198,126.6561375,58.0
106
+ 37.4499379,126.656248,56.0
107
+ 37.4485566,126.6557102,59.0
108
+ 37.4483627,126.6551062,42.0
109
+ 37.4506589,126.6570141,51.0
110
+ 37.4503802,126.656469,58.0
111
+ 37.4505559,126.6571835,48.0
112
+ 37.4492957,126.656329,58.0
113
+ 37.4495078,126.6564248,61.0
README.md CHANGED
@@ -1,12 +1,6 @@
1
  ---
2
- title: Sampling Proj
3
- emoji: ๐Ÿจ
4
- colorFrom: pink
5
- colorTo: blue
6
- sdk: gradio
7
- sdk_version: 6.0.1
8
  app_file: app.py
9
- pinned: false
 
10
  ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: Sampling_Proj
 
 
 
 
 
3
  app_file: app.py
4
+ sdk: gradio
5
+ sdk_version: 5.35.0
6
  ---
 
 
app.py ADDED
@@ -0,0 +1,539 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # sampling_sim_app.py
2
+ import os
3
+ import io
4
+
5
+ import matplotlib
6
+ matplotlib.use("Agg")
7
+
8
+ import numpy as np
9
+ import pandas as pd
10
+ import geopandas as gpd
11
+ import matplotlib.pyplot as plt
12
+ from shapely.geometry import Point
13
+ from shapely import wkt as shp_wkt
14
+ from PIL import Image
15
+ import contextily as ctx # โ† ์ถ”๊ฐ€
16
+
17
+ import gradio as gr
18
+
19
+ # ----------------- ๊ฒฝ๋กœ ์„ค์ • -----------------
20
+ BASE_DIR = os.path.dirname(__file__)
21
+ DATA_DIR = os.path.join(BASE_DIR, "Data")
22
+
23
+ SHP_PATH = os.path.join(DATA_DIR, "inha_boundary.shp")
24
+ ZONES_PATH = os.path.join(DATA_DIR, "zones_์ตœ์ข….geojson")
25
+
26
+ # ์ธก์ •๊ฒฐ๊ณผ CSV (lat, lon, dB ๋˜๋Š” ์œ„์น˜, dB)
27
+ CSV_PATH = os.path.join(DATA_DIR, "์ธก์ •๊ฒฐ๊ณผ_์ตœ์ข….csv")
28
+
29
+
30
+ # ---------- ์œ ํ‹ธ: Figure -> numpy ----------
31
+ def fig_to_array(fig, dpi=100):
32
+ buf = io.BytesIO()
33
+ fig.savefig(buf, format="png", dpi=dpi, bbox_inches="tight")
34
+ buf.seek(0)
35
+ img = np.array(Image.open(buf))
36
+ plt.close(fig)
37
+ return img
38
+
39
+
40
+ # ---------- ๊ฒฝ๊ณ„ SHP ๋กœ๋”ฉ ----------
41
+ def load_boundary():
42
+ gdf4326 = gpd.read_file(SHP_PATH).to_crs(epsg=4326)
43
+ gdf3857 = gdf4326.to_crs(epsg=3857)
44
+
45
+ poly3857 = gdf3857.geometry.unary_union
46
+ if poly3857.geom_type == "MultiPolygon":
47
+ poly3857 = max(poly3857.geoms, key=lambda g: g.area)
48
+
49
+ return gdf4326, gdf3857, poly3857
50
+
51
+
52
+ # ---------- ์ธก์ •๊ฒฐ๊ณผ CSV ๋กœ๋”ฉ ----------
53
+ def load_points_from_csv():
54
+ try:
55
+ df_u = pd.read_csv(CSV_PATH, encoding="cp949")
56
+ except UnicodeDecodeError:
57
+ df_u = pd.read_csv(CSV_PATH, encoding="utf-8")
58
+
59
+ df_u.columns = [c.strip() for c in df_u.columns]
60
+ df_u = df_u.loc[:, ~df_u.columns.str.contains("^Unnamed")]
61
+
62
+ # ์ผ€์ด์Šค B: ์œ„์น˜, dB
63
+ if "์œ„์น˜" in df_u.columns and "dB" in df_u.columns:
64
+ pos_series = df_u["์œ„์น˜"].astype(str).str.strip()
65
+
66
+ def safe_load_wkt(s):
67
+ s_up = s.upper()
68
+ if "POINT" not in s_up:
69
+ return None
70
+ try:
71
+ return shp_wkt.loads(s)
72
+ except Exception:
73
+ return None
74
+
75
+ geom = pos_series.apply(safe_load_wkt)
76
+ mask = geom.notnull()
77
+ if not mask.any():
78
+ raise ValueError("'์œ„์น˜' ์ปฌ๋Ÿผ์— ์œ ํšจํ•œ POINT WKT๊ฐ€ ์—†๋‹ค.")
79
+
80
+ geom_valid = geom[mask]
81
+ lons = geom_valid.apply(lambda g: round(g.x, 7))
82
+ lats = geom_valid.apply(lambda g: round(g.y, 7))
83
+ dBs = pd.to_numeric(df_u.loc[mask, "dB"], errors="coerce")
84
+
85
+ df = pd.DataFrame({"lat": lats, "lon": lons, "dB": dBs}).dropna(
86
+ subset=["lat", "lon"]
87
+ )
88
+ return df
89
+
90
+ # ์ผ€์ด์Šค A: lat, lon, dB
91
+ if {"lat", "lon", "dB"}.issubset(df_u.columns):
92
+ df = df_u[["lat", "lon", "dB"]].copy()
93
+ df["lat"] = pd.to_numeric(df["lat"], errors="coerce").round(7)
94
+ df["lon"] = pd.to_numeric(df["lon"], errors="coerce").round(7)
95
+ df["dB"] = pd.to_numeric(df["dB"], errors="coerce")
96
+ df = df.dropna(subset=["lat", "lon"])
97
+ return df
98
+
99
+ raise ValueError("CSV๋Š” (lat, lon, dB) ๋˜๋Š” (์œ„์น˜, dB) ํ˜•์‹์ด์–ด์•ผ ํ•œ๋‹ค.")
100
+
101
+
102
+ # ---------- ๊ตฌ์—ญ GeoJSON ๋กœ๋”ฉ ----------
103
+ def load_zones(poly3857):
104
+ zones4326 = gpd.read_file(ZONES_PATH).to_crs(epsg=4326)
105
+ zones3857 = zones4326.to_crs(epsg=3857)
106
+
107
+ if "zone_id" not in zones3857.columns:
108
+ zones3857["zone_id"] = np.arange(1, len(zones3857) + 1)
109
+
110
+ zones3857["geom_clip"] = zones3857.geometry.intersection(poly3857)
111
+ zones3857["area"] = zones3857["geom_clip"].area
112
+ zones3857 = zones3857[zones3857["area"] > 0].copy()
113
+
114
+ A_total = zones3857["area"].sum()
115
+ zones3857["W_area"] = zones3857["area"] / A_total
116
+
117
+ return zones4326, zones3857
118
+
119
+
120
+ # ---------- ํฌ์ธํŠธ์— zone_id ๋ถ™์ด๊ธฐ ----------
121
+ def attach_zone_id(points_df, zones3857):
122
+ pts4326 = gpd.GeoDataFrame(
123
+ points_df.copy(),
124
+ geometry=gpd.points_from_xy(points_df["lon"], points_df["lat"]),
125
+ crs=4326,
126
+ )
127
+ pts3857 = pts4326.to_crs(epsg=3857)
128
+
129
+ zones_clip = zones3857[["zone_id", "geom_clip"]].copy()
130
+ zones_clip = zones_clip.set_geometry("geom_clip")
131
+ zones_clip = zones_clip.rename_geometry("geometry")
132
+
133
+ joined = gpd.sjoin(
134
+ pts3857,
135
+ zones_clip[["zone_id", "geometry"]],
136
+ how="inner",
137
+ predicate="within",
138
+ )
139
+
140
+ out = joined[["lat", "lon", "dB", "zone_id"]].copy().reset_index(drop=True)
141
+ return out
142
+
143
+
144
+ # ---------- ๊ณตํ†ต ์ค€๋น„: ๊ฒฝ๊ณ„/๊ตฌ์—ญ/ํฌ์ธํŠธ/์ธต์ •๋ณด ----------
145
+ def prepare_sampling_data():
146
+ boundary4326, boundary3857, poly3857 = load_boundary()
147
+ zones4326, zones3857 = load_zones(poly3857)
148
+ df_points = load_points_from_csv()
149
+ points_zone = attach_zone_id(df_points, zones3857)
150
+
151
+ # ์ธต๋ณ„ ๋ชจ์ง‘๋‹จ ํฌ๊ธฐ N_h
152
+ zone_sizes = points_zone.groupby("zone_id").size().rename("N_h")
153
+ zones_info = zones3857[["zone_id", "area", "W_area"]].merge(
154
+ zone_sizes, on="zone_id", how="left"
155
+ )
156
+ zones_info["N_h"] = zones_info["N_h"].fillna(0).astype(int)
157
+
158
+ # ์ธต๋ณ„ ํ‘œ์ค€ํŽธ์ฐจ S_h (๋„ค์ด๋งŒ ๋ฐฐ๋ถ„์šฉ)
159
+ strata_std = points_zone.groupby("zone_id")["dB"].std(ddof=1).rename("S_h")
160
+ zones_info = zones_info.merge(strata_std, on="zone_id", how="left")
161
+ zones_info["S_h"] = zones_info["S_h"].fillna(0.0)
162
+
163
+ return boundary4326, zones4326, df_points, points_zone, zones_info
164
+
165
+
166
+ # ---------- ๋ฉด์ ๋น„๋ก€ n_h ๊ณ„์‚ฐ ----------
167
+ def allocate_n_by_area(zones_info, n_total):
168
+ zones = zones_info.copy()
169
+ zones["n_raw"] = n_total * zones["W_area"]
170
+ zones["n"] = zones["n_raw"].round().astype(int)
171
+
172
+ diff = n_total - zones["n"].sum()
173
+ if diff != 0:
174
+ order = zones["n_raw"].sort_values(ascending=(diff < 0)).index
175
+ for idx in order[:abs(diff)]:
176
+ zones.loc[idx, "n"] += 1 if diff > 0 else -1
177
+
178
+ zones["n"] = zones[["n", "N_h"]].min(axis=1)
179
+ return zones
180
+
181
+
182
+ # ---------- ๋„ค์ด๋งŒ ๋ฐฐ๋ถ„ n_h ๊ณ„์‚ฐ ----------
183
+ def allocate_n_neyman(zones_info, n_total):
184
+ zones = zones_info.copy()
185
+ if "S_h" not in zones.columns:
186
+ raise ValueError("zones_info์— S_h๊ฐ€ ์—†๋‹ค. ๋„ค์ด๋งŒ ๋ฐฐ๋ถ„ ์ „์— S_h๋ฅผ ๊ณ„์‚ฐํ•ด์•ผ ํ•œ๋‹ค.")
187
+
188
+ zones["S_h"] = zones["S_h"].fillna(0.0)
189
+
190
+ # ๊ฐ€์ค‘์น˜ โˆ N_h * S_h
191
+ weight = zones["N_h"] * zones["S_h"]
192
+ if weight.sum() <= 0:
193
+ # ๋ถ„์‚ฐ์ด ์ „๋ถ€ 0์ด๋ฉด N_h ๋น„๋ก€๋กœ ๋ฐฐ๋ถ„
194
+ weight = zones["N_h"].copy()
195
+
196
+ zones["n_raw"] = n_total * weight / weight.sum()
197
+ zones["n"] = zones["n_raw"].round().astype(int)
198
+
199
+ diff = n_total - zones["n"].sum()
200
+ if diff != 0:
201
+ order = zones["n_raw"].sort_values(ascending=(diff < 0)).index
202
+ for idx in order[:abs(diff)]:
203
+ zones.loc[idx, "n"] += 1 if diff > 0 else -1
204
+
205
+ zones["n"] = zones[["n", "N_h"]].min(axis=1)
206
+ return zones
207
+
208
+
209
+ # ---------- ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ----------
210
+ def simulate_sampling(points_zone_df, zones_info, n, R, strat_label):
211
+ y_all = points_zone_df["dB"].to_numpy()
212
+ N = len(y_all)
213
+ mu_pop = float(np.mean(y_all))
214
+
215
+ zones = zones_info.set_index("zone_id")
216
+ W_h = zones["N_h"] / zones["N_h"].sum()
217
+ n_h = zones["n"]
218
+
219
+ idx_by_zone = {}
220
+ for z in zones.index:
221
+ idx_by_zone[z] = points_zone_df.index[
222
+ points_zone_df["zone_id"] == z
223
+ ].to_numpy()
224
+
225
+ srs_means = []
226
+ strat_means = []
227
+
228
+ for _ in range(R):
229
+ # SRS
230
+ n_eff = min(n, N)
231
+ idx_srs = np.random.choice(N, size=n_eff, replace=False)
232
+ mu_srs = float(np.mean(y_all[idx_srs]))
233
+ srs_means.append(mu_srs)
234
+
235
+ # Stratified
236
+ mu_str = 0.0
237
+ for z in zones.index:
238
+ N_h = int(zones.loc[z, "N_h"])
239
+ nh = int(n_h.loc[z])
240
+ if N_h == 0 or nh == 0:
241
+ continue
242
+ idx_pool = idx_by_zone[z]
243
+ if len(idx_pool) < nh:
244
+ nh = len(idx_pool)
245
+ choose = np.random.choice(idx_pool, size=nh, replace=False)
246
+ y_h = points_zone_df.loc[choose, "dB"].to_numpy()
247
+ ybar_h = float(np.mean(y_h))
248
+ mu_str += float(W_h.loc[z]) * ybar_h
249
+ strat_means.append(mu_str)
250
+
251
+ srs_means = np.array(srs_means)
252
+ strat_means = np.array(strat_means)
253
+
254
+ def metrics(arr, name):
255
+ mean_hat = float(arr.mean())
256
+ var_hat = float(arr.var(ddof=1))
257
+ bias = mean_hat - mu_pop
258
+ mse = float(((arr - mu_pop) ** 2).mean())
259
+ return {
260
+ "design": name,
261
+ "mean_hat": mean_hat,
262
+ "var_hat": var_hat,
263
+ "bias": bias,
264
+ "MSE": mse,
265
+ }
266
+
267
+ m_srs = metrics(srs_means, "SRS")
268
+ m_str = metrics(strat_means, strat_label)
269
+
270
+ df_metrics = pd.DataFrame([m_srs, m_str])
271
+ df_metrics["deff_vs_SRS"] = df_metrics["var_hat"] / df_metrics["var_hat"].iloc[0]
272
+
273
+ # ์ˆซ์ž 4์งธ ์ž๋ฆฌ๊นŒ์ง€ ๋ฐ˜์˜ฌ๋ฆผ
274
+ num_cols = ["mean_hat", "var_hat", "bias", "MSE", "deff_vs_SRS"]
275
+ df_metrics[num_cols] = df_metrics[num_cols].round(4)
276
+
277
+ return mu_pop, srs_means, strat_means, df_metrics
278
+
279
+
280
+ # ---------- ๊ตฌ์—ญ ์š”์•ฝ ํ…Œ์ด๋ธ” ----------
281
+ def build_zones_table(zones_alloc):
282
+ zones_table = zones_alloc[["zone_id", "area", "W_area", "N_h", "n"]].copy()
283
+ zones_table = zones_table.rename(
284
+ columns={
285
+ "area": "area_m2",
286
+ "W_area": "area_weight",
287
+ "N_h": "N_pop",
288
+ "n": "n_alloc",
289
+ }
290
+ )
291
+ zones_table["sampling_frac"] = zones_table["n_alloc"] / zones_table["N_pop"]
292
+
293
+ total_area_m2 = zones_table["area_m2"].sum()
294
+ total_area_weight = zones_table["area_weight"].sum()
295
+ total_N = zones_table["N_pop"].sum()
296
+ total_n = zones_table["n_alloc"].sum()
297
+ total_sampling_frac = total_n / total_N if total_N > 0 else np.nan
298
+
299
+ total_row = {
300
+ "zone_id": "๏ฟฝ๏ฟฝ๏ฟฝ๊ณ„",
301
+ "area_m2": total_area_m2,
302
+ "area_weight": total_area_weight,
303
+ "N_pop": total_N,
304
+ "n_alloc": total_n,
305
+ "sampling_frac": total_sampling_frac,
306
+ }
307
+
308
+ zones_table = zones_table[
309
+ ["zone_id", "area_m2", "area_weight", "N_pop", "n_alloc", "sampling_frac"]
310
+ ]
311
+ zones_table = pd.concat([zones_table, pd.DataFrame([total_row])], ignore_index=True)
312
+
313
+ # ์ˆซ์ž ์ปฌ๋Ÿผ 4์งธ ์ž๋ฆฌ๊นŒ์ง€ ๋ฐ˜์˜ฌ๋ฆผ
314
+ num_cols_zone = ["area_m2", "area_weight", "N_pop", "n_alloc", "sampling_frac"]
315
+ for col in num_cols_zone:
316
+ zones_table[col] = pd.to_numeric(zones_table[col], errors="coerce").round(4)
317
+
318
+ return zones_table, float(total_N), float(total_n)
319
+
320
+
321
+ # ---------- ์‹œ๊ฐํ™” ----------
322
+ def plot_population_map(boundary4326, zones4326, points_df):
323
+ g_boundary = boundary4326.to_crs(epsg=3857)
324
+ g_zones = zones4326.to_crs(epsg=3857)
325
+ g_pts = gpd.GeoDataFrame(
326
+ points_df.copy(),
327
+ geometry=gpd.points_from_xy(points_df["lon"], points_df["lat"]),
328
+ crs=4326,
329
+ ).to_crs(epsg=3857)
330
+
331
+ fig, ax = plt.subplots(figsize=(8, 8))
332
+
333
+ # ๋ฒ ์ด์Šค๋งต ๋จผ์ € ๊น”๊ธฐ
334
+ minx, miny, maxx, maxy = g_boundary.total_bounds
335
+ ax.set_xlim(minx, maxx)
336
+ ax.set_ylim(miny, maxy)
337
+ ctx.add_basemap(ax, source=ctx.providers.OpenStreetMap.Mapnik, alpha=0.7)
338
+
339
+ # ๊ทธ ์œ„์— ๊ฒฝ๊ณ„/๊ตฌ์—ญ/ํฌ์ธํŠธ
340
+ g_boundary.plot(ax=ax, facecolor="none", edgecolor="black", linewidth=2)
341
+ if len(g_zones) > 0:
342
+ g_zones.plot(
343
+ ax=ax,
344
+ facecolor="none",
345
+ edgecolor="red",
346
+ linewidth=1.5,
347
+ alpha=0.7,
348
+ )
349
+
350
+ g_pts.plot(
351
+ ax=ax,
352
+ column="dB",
353
+ cmap="viridis",
354
+ markersize=20,
355
+ edgecolor="black",
356
+ linewidth=0.2,
357
+ legend=False,
358
+ alpha=0.9,
359
+ )
360
+
361
+ ax.set_title("Population points with zones", fontsize=12)
362
+ ax.axis("off")
363
+ plt.tight_layout()
364
+ return fig_to_array(fig)
365
+
366
+
367
+ def plot_histograms(srs_means, strat_means, mu_pop, strat_label):
368
+ fig, ax = plt.subplots(figsize=(8, 5))
369
+
370
+ ax.hist(srs_means, bins=20, alpha=0.5, label="SRS")
371
+ ax.hist(strat_means, bins=20, alpha=0.5, label=strat_label)
372
+ ax.axvline(mu_pop, color="k", linestyle="--", label="Population mean")
373
+
374
+ ax.set_xlabel("Sample mean (dB)")
375
+ ax.set_ylabel("Frequency")
376
+ ax.legend()
377
+ ax.set_title("Sampling distribution of mean")
378
+ plt.tight_layout()
379
+ return fig_to_array(fig)
380
+
381
+
382
+ # ---------- Gradio ์ฝœ๋ฐฑ: ๋ฉด์ ๋น„๋ก€ ----------
383
+ def run_sim_area(n, R):
384
+ n = int(n)
385
+ R = int(R)
386
+
387
+ boundary4326, zones4326, df_points, points_zone, zones_info = prepare_sampling_data()
388
+
389
+ # ๋ฉด์ ๋น„๋ก€ ๋ฐฐ๋ถ„
390
+ zones_alloc = allocate_n_by_area(zones_info, n)
391
+ zones_table, total_N, total_n = build_zones_table(zones_alloc)
392
+
393
+ mu_pop, srs_means, strat_means, df_metrics = simulate_sampling(
394
+ points_zone, zones_alloc, n, R, "Stratified (area-proportional)"
395
+ )
396
+
397
+ map_img = plot_population_map(boundary4326, zones4326, df_points)
398
+ hist_img = plot_histograms(
399
+ srs_means, strat_means, mu_pop, "Stratified (area-proportional)"
400
+ )
401
+
402
+ summary = (
403
+ f"[๋ฉด์ ๋น„๋ก€ ์ธตํ™”]\n"
404
+ f"๋ชจ์ง‘๋‹จ ํฌ์ธํŠธ ์ˆ˜: {int(total_N)}๊ฐœ\n"
405
+ f"๊ตฌ์—ญ ์ˆ˜: {len(zones_alloc)}๊ฐœ\n"
406
+ f"๋ชจ์ง‘๋‹จ ํ‰๊ท  dB: {mu_pop:.4f}\n"
407
+ f"์ด ํ‘œ๋ณธํฌ๊ธฐ n = {int(total_n)} (์ž…๋ ฅ๊ฐ’ {n}), ๋ฐ˜๋ณต R = {R}"
408
+ )
409
+
410
+ return map_img, hist_img, df_metrics, zones_table, summary
411
+
412
+
413
+ # ---------- Gradio ์ฝœ๋ฐฑ: ๋„ค์ด๋งŒ ----------
414
+ def run_sim_neyman(n, R):
415
+ n = int(n)
416
+ R = int(R)
417
+
418
+ boundary4326, zones4326, df_points, points_zone, zones_info = prepare_sampling_data()
419
+
420
+ # ๋„ค์ด๋งŒ ๋ฐฐ๋ถ„
421
+ zones_alloc = allocate_n_neyman(zones_info, n)
422
+ zones_table, total_N, total_n = build_zones_table(zones_alloc)
423
+
424
+ mu_pop, srs_means, strat_means, df_metrics = simulate_sampling(
425
+ points_zone, zones_alloc, n, R, "Stratified (Neyman)"
426
+ )
427
+
428
+ map_img = plot_population_map(boundary4326, zones4326, df_points)
429
+ hist_img = plot_histograms(
430
+ srs_means, strat_means, mu_pop, "Stratified (Neyman)"
431
+ )
432
+
433
+ summary = (
434
+ f"[๋„ค์ด๋งŒ ์ธตํ™”]\n"
435
+ f"๋ชจ์ง‘๋‹จ ํฌ์ธํŠธ ์ˆ˜: {int(total_N)}๊ฐœ\n"
436
+ f"๊ตฌ์—ญ ์ˆ˜: {len(zones_alloc)}๊ฐœ\n"
437
+ f"๋ชจ์ง‘๋‹จ ํ‰๊ท  dB: {mu_pop:.4f}\n"
438
+ f"์ด ํ‘œ๋ณธํฌ๊ธฐ n = {int(total_n)} (์ž…๋ ฅ๊ฐ’ {n}), ๋ฐ˜๋ณต R = {R}"
439
+ )
440
+
441
+ return map_img, hist_img, df_metrics, zones_table, summary
442
+
443
+
444
+ # ---------- Gradio ์•ฑ ----------
445
+ def build_app():
446
+ with gr.Blocks(title="ํ‘œ๋ณธ์ถ”์ถœ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ (SRS vs ์ธตํ™”)") as demo:
447
+
448
+ with gr.Tab("SRS vs ๋ฉด์ ๋น„๋ก€ ์ธตํ™”"):
449
+ with gr.Row():
450
+ n_area = gr.Number(label="ํ‘œ๋ณธํฌ๊ธฐ n", value=60, precision=0)
451
+ R_area = gr.Number(label="์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ฐ˜๋ณต ํšŸ์ˆ˜ R", value=500, precision=0)
452
+
453
+ run_btn_area = gr.Button("๋ฉด์ ๋น„๋ก€ ์ธตํ™” ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์‹คํ–‰", variant="primary")
454
+
455
+ with gr.Row():
456
+ map_img_area = gr.Image(label="Population + zones")
457
+ hist_img_area = gr.Image(label="Sampling distribution")
458
+
459
+ metrics_table_area = gr.Dataframe(
460
+ headers=[
461
+ "design",
462
+ "mean_hat",
463
+ "var_hat",
464
+ "bias",
465
+ "MSE",
466
+ "deff_vs_SRS",
467
+ ],
468
+ label="์„ฑ๋Šฅ ๋น„๊ต ์ง€ํ‘œ",
469
+ interactive=False,
470
+ )
471
+
472
+ zones_table_area = gr.Dataframe(
473
+ label="๊ตฌ์—ญ๋ณ„ ๋ฉด์  ๋ฐ ๋ชจ์ง‘๋‹จ/๋ฐฐ์ • ํ‘œ๋ณธ์ˆ˜",
474
+ interactive=False,
475
+ )
476
+
477
+ summary_md_area = gr.Markdown()
478
+
479
+ run_btn_area.click(
480
+ fn=run_sim_area,
481
+ inputs=[n_area, R_area],
482
+ outputs=[
483
+ map_img_area,
484
+ hist_img_area,
485
+ metrics_table_area,
486
+ zones_table_area,
487
+ summary_md_area,
488
+ ],
489
+ )
490
+
491
+ with gr.Tab("SRS vs ๋„ค์ด๋งŒ ์ธตํ™”"):
492
+ with gr.Row():
493
+ n_neyman = gr.Number(label="ํ‘œ๋ณธํฌ๊ธฐ n", value=60, precision=0)
494
+ R_neyman = gr.Number(label="์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ฐ˜๋ณต ํšŸ์ˆ˜ R", value=500, precision=0)
495
+
496
+ run_btn_neyman = gr.Button("๋„ค์ด๋งŒ ์ธตํ™” ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์‹คํ–‰", variant="primary")
497
+
498
+ with gr.Row():
499
+ map_img_neyman = gr.Image(label="Population + zones")
500
+ hist_img_neyman = gr.Image(label="Sampling distribution")
501
+
502
+ metrics_table_neyman = gr.Dataframe(
503
+ headers=[
504
+ "design",
505
+ "mean_hat",
506
+ "var_hat",
507
+ "bias",
508
+ "MSE",
509
+ "deff_vs_SRS",
510
+ ],
511
+ label="์„ฑ๋Šฅ ๋น„๊ต ์ง€ํ‘œ",
512
+ interactive=False,
513
+ )
514
+
515
+ zones_table_neyman = gr.Dataframe(
516
+ label="๊ตฌ์—ญ๋ณ„ ๋ฉด์  ๋ฐ ๋ชจ์ง‘๋‹จ/๋ฐฐ์ • ํ‘œ๋ณธ์ˆ˜",
517
+ interactive=False,
518
+ )
519
+
520
+ summary_md_neyman = gr.Markdown()
521
+
522
+ run_btn_neyman.click(
523
+ fn=run_sim_neyman,
524
+ inputs=[n_neyman, R_neyman],
525
+ outputs=[
526
+ map_img_neyman,
527
+ hist_img_neyman,
528
+ metrics_table_neyman,
529
+ zones_table_neyman,
530
+ summary_md_neyman,
531
+ ],
532
+ )
533
+
534
+ return demo
535
+
536
+
537
+ if __name__ == "__main__":
538
+ app = build_app()
539
+ app.launch(share=True)
draw_zones.html ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+
5
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
6
+ <script src="https://cdn.jsdelivr.net/npm/leaflet@1.9.3/dist/leaflet.js"></script>
7
+ <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.js"></script>
10
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet@1.9.3/dist/leaflet.css"/>
11
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css"/>
12
+ <link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css"/>
13
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.2.0/css/all.min.css"/>
14
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css"/>
15
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/python-visualization/folium/folium/templates/leaflet.awesome.rotate.min.css"/>
16
+
17
+ <meta name="viewport" content="width=device-width,
18
+ initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
19
+ <style>
20
+ #map_78266fbe1759aea8d6c235f8af0c0d47 {
21
+ position: relative;
22
+ width: 100.0%;
23
+ height: 100.0%;
24
+ left: 0.0%;
25
+ top: 0.0%;
26
+ }
27
+ .leaflet-container { font-size: 1rem; }
28
+ </style>
29
+
30
+ <style>html, body {
31
+ width: 100%;
32
+ height: 100%;
33
+ margin: 0;
34
+ padding: 0;
35
+ }
36
+ </style>
37
+
38
+ <style>#map {
39
+ position:absolute;
40
+ top:0;
41
+ bottom:0;
42
+ right:0;
43
+ left:0;
44
+ }
45
+ </style>
46
+
47
+ <script>
48
+ L_NO_TOUCH = false;
49
+ L_DISABLE_3D = false;
50
+ </script>
51
+
52
+
53
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.2/leaflet.draw.js"></script>
54
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.2/leaflet.draw.css"/>
55
+ </head>
56
+ <body>
57
+
58
+
59
+ <div class="folium-map" id="map_78266fbe1759aea8d6c235f8af0c0d47" ></div>
60
+
61
+
62
+
63
+ <style>
64
+ #export {
65
+ position: absolute;
66
+ top: 5px;
67
+ right: 10px;
68
+ z-index: 999;
69
+ background: white;
70
+ color: black;
71
+ padding: 6px;
72
+ border-radius: 4px;
73
+ font-family: 'Helvetica Neue';
74
+ cursor: pointer;
75
+ font-size: 12px;
76
+ text-decoration: none;
77
+ top: 90px;
78
+ }
79
+ </style>
80
+ <a href='#' id='export'>Export</a>
81
+
82
+
83
+ </body>
84
+ <script>
85
+
86
+
87
+ var map_78266fbe1759aea8d6c235f8af0c0d47 = L.map(
88
+ "map_78266fbe1759aea8d6c235f8af0c0d47",
89
+ {
90
+ center: [37.44975286338557, 126.65343455495042],
91
+ crs: L.CRS.EPSG3857,
92
+ ...{
93
+ "zoom": 17,
94
+ "zoomControl": true,
95
+ "preferCanvas": false,
96
+ }
97
+
98
+ }
99
+ );
100
+
101
+
102
+
103
+
104
+
105
+ var tile_layer_c1196f4c91b6a6393d3df89cf55b8c8f = L.tileLayer(
106
+ "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
107
+ {
108
+ "minZoom": 0,
109
+ "maxZoom": 19,
110
+ "maxNativeZoom": 19,
111
+ "noWrap": false,
112
+ "attribution": "\u0026copy; \u003ca href=\"https://www.openstreetmap.org/copyright\"\u003eOpenStreetMap\u003c/a\u003e contributors",
113
+ "subdomains": "abc",
114
+ "detectRetina": false,
115
+ "tms": false,
116
+ "opacity": 1,
117
+ }
118
+
119
+ );
120
+
121
+
122
+ tile_layer_c1196f4c91b6a6393d3df89cf55b8c8f.addTo(map_78266fbe1759aea8d6c235f8af0c0d47);
123
+
124
+
125
+ function geo_json_9b5f5972b80e71540402a2523e8a23d1_styler(feature) {
126
+ switch(feature.id) {
127
+ default:
128
+ return {"color": "blue", "fill": false, "weight": 3};
129
+ }
130
+ }
131
+
132
+ function geo_json_9b5f5972b80e71540402a2523e8a23d1_onEachFeature(feature, layer) {
133
+
134
+ layer.on({
135
+ });
136
+ };
137
+ var geo_json_9b5f5972b80e71540402a2523e8a23d1 = L.geoJson(null, {
138
+ onEachFeature: geo_json_9b5f5972b80e71540402a2523e8a23d1_onEachFeature,
139
+
140
+ style: geo_json_9b5f5972b80e71540402a2523e8a23d1_styler,
141
+ ...{
142
+ }
143
+ });
144
+
145
+ function geo_json_9b5f5972b80e71540402a2523e8a23d1_add (data) {
146
+ geo_json_9b5f5972b80e71540402a2523e8a23d1
147
+ .addData(data);
148
+ }
149
+ geo_json_9b5f5972b80e71540402a2523e8a23d1_add({"features": [{"geometry": {"coordinates": [[[126.6504913, 37.4488536], [126.6502018, 37.4490381], [126.6501274, 37.4490101], [126.650006, 37.449035], [126.6499295, 37.4493095], [126.6497967, 37.4495766], [126.6496642, 37.4498464], [126.6493324, 37.4504076], [126.6490067, 37.4509487], [126.649781, 37.4514003], [126.6495914, 37.4517312], [126.6501326, 37.45175], [126.6505999, 37.4517726], [126.6510982, 37.4518266], [126.6512838, 37.4518467], [126.6517828, 37.4519342], [126.6520048, 37.4519731], [126.6520885, 37.4519878], [126.6524237, 37.4520465], [126.6525693, 37.452072], [126.6528855, 37.4521371], [126.6530797, 37.4521045], [126.6536373, 37.4518765], [126.6544021, 37.4516675], [126.6564271, 37.4511471], [126.6578465, 37.4507776], [126.6569217, 37.4492734], [126.6553859, 37.4466845], [126.6547144, 37.4468884], [126.6545294, 37.4469713], [126.6544155, 37.4470167], [126.653386, 37.4474272], [126.6527874, 37.4476634], [126.6507639, 37.4484888], [126.6505808, 37.4485626], [126.6505271, 37.4487565], [126.6504913, 37.4488536]]], "type": "Polygon"}, "id": "0", "type": "Feature"}], "type": "FeatureCollection"});
150
+
151
+
152
+
153
+ geo_json_9b5f5972b80e71540402a2523e8a23d1.addTo(map_78266fbe1759aea8d6c235f8af0c0d47);
154
+
155
+
156
+ var options = {
157
+ position: "topleft",
158
+ draw: {"circle": false, "circlemarker": false, "marker": false, "polygon": true, "polyline": false, "rectangle": true},
159
+ edit: {"edit": true, "remove": true},
160
+ }
161
+ // FeatureGroup is to store editable layers.
162
+ var drawnItems_draw_control_9d017ca223827f6de1a10b444cecfa37 =
163
+ new L.featureGroup().addTo(
164
+ map_78266fbe1759aea8d6c235f8af0c0d47
165
+ );
166
+
167
+ options.edit.featureGroup = drawnItems_draw_control_9d017ca223827f6de1a10b444cecfa37;
168
+ var draw_control_9d017ca223827f6de1a10b444cecfa37 = new L.Control.Draw(
169
+ options
170
+ ).addTo( map_78266fbe1759aea8d6c235f8af0c0d47 );
171
+ map_78266fbe1759aea8d6c235f8af0c0d47.on(L.Draw.Event.CREATED, function(e) {
172
+ var layer = e.layer,
173
+ type = e.layerType;
174
+ var coords = JSON.stringify(layer.toGeoJSON());
175
+ layer.on('click', function() {
176
+ alert(coords);
177
+ console.log(coords);
178
+ });
179
+ drawnItems_draw_control_9d017ca223827f6de1a10b444cecfa37.addLayer(layer);
180
+ });
181
+ map_78266fbe1759aea8d6c235f8af0c0d47.on('draw:created', function(e) {
182
+ drawnItems_draw_control_9d017ca223827f6de1a10b444cecfa37.addLayer(e.layer);
183
+ });
184
+
185
+
186
+ document.getElementById('export').onclick = function(e) {
187
+ var data = drawnItems_draw_control_9d017ca223827f6de1a10b444cecfa37.toGeoJSON();
188
+ var convertedData = 'text/json;charset=utf-8,'
189
+ + encodeURIComponent(JSON.stringify(data));
190
+ document.getElementById('export').setAttribute(
191
+ 'href', 'data:' + convertedData
192
+ );
193
+ document.getElementById('export').setAttribute(
194
+ 'download', "zones.geojson"
195
+ );
196
+ }
197
+
198
+
199
+
200
+ var layer_control_859bc6ff55e56629719267d89d3e2a9c_layers = {
201
+ base_layers : {
202
+ "openstreetmap" : tile_layer_c1196f4c91b6a6393d3df89cf55b8c8f,
203
+ },
204
+ overlays : {
205
+ "campus_boundary" : geo_json_9b5f5972b80e71540402a2523e8a23d1,
206
+ },
207
+ };
208
+ let layer_control_859bc6ff55e56629719267d89d3e2a9c = L.control.layers(
209
+ layer_control_859bc6ff55e56629719267d89d3e2a9c_layers.base_layers,
210
+ layer_control_859bc6ff55e56629719267d89d3e2a9c_layers.overlays,
211
+ {
212
+ "position": "topright",
213
+ "collapsed": true,
214
+ "autoZIndex": true,
215
+ }
216
+ ).addTo(map_78266fbe1759aea8d6c235f8af0c0d47);
217
+
218
+
219
+ </script>
220
+ </html>
geo.ipynb ADDED
File without changes
pin.py ADDED
@@ -0,0 +1,376 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+
4
+ import matplotlib
5
+ matplotlib.use("Agg") # ๋ฐฑ์—”๋“œ Agg๋กœ ๊ณ ์ •
6
+
7
+ import geopandas as gpd
8
+ import pandas as pd
9
+ import numpy as np
10
+ import matplotlib.pyplot as plt
11
+ import contextily as ctx
12
+ from shapely.geometry import Point
13
+ from shapely import wkt as shp_wkt
14
+ from PIL import Image
15
+
16
+ import gradio as gr
17
+
18
+ # -------------------- ๊ฒฝ๋กœ ์„ค์ • --------------------
19
+ SHP_PATH = r"C:\Users\USER\iCloudDrive\์ธํ•˜๋Œ€ํ•™๊ต\5ํ•™๋…„ 1ํ•™๊ธฐ\ํ‘œ๋ณธ๋ก  ๋ฐ ์‹ค์Šต\TeamProject\Data\inha_boundary.shp"
20
+ CSV_PATH = r"C:\Users\USER\iCloudDrive\์ธํ•˜๋Œ€ํ•™๊ต\5ํ•™๋…„ 1ํ•™๊ธฐ\ํ‘œ๋ณธ๋ก  ๋ฐ ์‹ค์Šต\TeamProject\๋ฐ์ดํ„ฐ ์ˆ˜์ง‘\์ธก์ •๊ฒฐ๊ณผ.csv"
21
+
22
+ # ์ด๋ฏธ์ง€ ํ•ด์ƒ๋„(ํ”ฝ์…€)
23
+ IMG_W, IMG_H = 1200, 900
24
+
25
+ # -------------------- ์บ ํผ์Šค ๊ฒฝ๊ณ„ ์ค€๋น„ --------------------
26
+ gdf_boundary_4326 = gpd.read_file(SHP_PATH).to_crs(epsg=4326)
27
+ gdf_boundary_3857 = gdf_boundary_4326.to_crs(epsg=3857)
28
+
29
+ # DeprecationWarning ํ”ผํ•˜๊ธฐ ์œ„ํ•ด union_all ์šฐ์„  ์‚ฌ์šฉ
30
+ try:
31
+ poly_3857 = gdf_boundary_3857.geometry.union_all()
32
+ except AttributeError:
33
+ poly_3857 = gdf_boundary_3857.geometry.unary_union
34
+
35
+ if poly_3857.geom_type == "MultiPolygon":
36
+ poly_3857 = max(poly_3857.geoms, key=lambda g: g.area)
37
+
38
+ BOUND_MINX, BOUND_MINY, BOUND_MAXX, BOUND_MAXY = poly_3857.bounds
39
+
40
+ # -------------------- ์ดˆ๊ธฐ CSV ๋กœ๋”ฉ --------------------
41
+ # -------------------- ์ดˆ๊ธฐ CSV ๋กœ๋”ฉ --------------------
42
+ if os.path.exists(CSV_PATH):
43
+ df_u = pd.read_csv(CSV_PATH, encoding="cp949")
44
+
45
+ # ์ปฌ๋Ÿผ ์ •๋ฆฌ
46
+ df_u.columns = [c.strip() for c in df_u.columns]
47
+ df_u = df_u.loc[:, ~df_u.columns.str.contains("^Unnamed")]
48
+
49
+ if {"lat", "lon", "dB"}.issubset(df_u.columns):
50
+ # ์ด๋ฏธ lat/lon/dB ํ˜•์‹์ด๋ฉด ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ
51
+ df_init = df_u[["lat", "lon", "dB"]].copy()
52
+ elif "์œ„์น˜" in df_u.columns and "dB" in df_u.columns:
53
+ # ์˜›๋‚  ํ˜•์‹: ์œ„์น˜(WKT), dB โ†’ lat/lon/dB๋กœ ๋ณ€ํ™˜
54
+ pos_series = df_u["์œ„์น˜"].astype(str).str.strip()
55
+
56
+ def safe_load_wkt(s):
57
+ s_up = s.upper()
58
+ if "POINT" not in s_up:
59
+ return None
60
+ try:
61
+ return shp_wkt.loads(s)
62
+ except Exception:
63
+ return None
64
+
65
+ geom = pos_series.apply(safe_load_wkt)
66
+ mask = geom.notnull()
67
+
68
+ if mask.any():
69
+ geom_valid = geom[mask]
70
+ lons = geom_valid.apply(lambda g: round(g.x, 7))
71
+ lats = geom_valid.apply(lambda g: round(g.y, 7))
72
+ dB_vals = pd.to_numeric(df_u.loc[mask, "dB"], errors="coerce")
73
+
74
+ df_init = pd.DataFrame(
75
+ {
76
+ "lat": lats.values,
77
+ "lon": lons.values,
78
+ "dB": dB_vals.values,
79
+ }
80
+ ).dropna(subset=["lat", "lon"])
81
+ else:
82
+ df_init = pd.DataFrame(columns=["lat", "lon", "dB"])
83
+ else:
84
+ # ํ˜•์‹์„ ์•Œ์•„๋ณผ ์ˆ˜ ์—†์œผ๋ฉด ๋นˆ DF
85
+ df_init = pd.DataFrame(columns=["lat", "lon", "dB"])
86
+ else:
87
+ df_init = pd.DataFrame(columns=["lat", "lon", "dB"])
88
+
89
+ # ํƒ€์ž… ๋ณด์ •
90
+ for col in ["lat", "lon", "dB"]:
91
+ if col not in df_init.columns:
92
+ df_init[col] = pd.Series(dtype=float)
93
+
94
+
95
+
96
+ # -------------------- ์ง€๋„ ์ด๋ฏธ์ง€ ์ƒ์„ฑ --------------------
97
+ def make_map_image(df_points: pd.DataFrame):
98
+ """์บ ํผ์Šค ๊ฒฝ๊ณ„ + ์ €์žฅ๋œ ํฌ์ธํŠธ๋“ค์„ ๊ทธ๋ฆฐ PNG ์ด๋ฏธ์ง€๋ฅผ numpy ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜."""
99
+ fig, ax = plt.subplots(
100
+ figsize=(IMG_W / 100, IMG_H / 100), dpi=100
101
+ ) # โ†’ ์ •ํ™•ํžˆ IMG_W x IMG_H ํ”ฝ์…€
102
+
103
+ # ์บ ํผ์Šค ํด๋ฆฌ๊ณค (ํ…Œ๋‘๋ฆฌ๋งŒ)
104
+ gdf_boundary_3857.plot(
105
+ ax=ax,
106
+ facecolor="none",
107
+ edgecolor="darkblue",
108
+ linewidth=2,
109
+ alpha=1.0,
110
+ )
111
+
112
+ # ๋ฒ ์ด์Šค๋งต
113
+ ctx.add_basemap(ax, source=ctx.providers.OpenStreetMap.Mapnik, alpha=0.7)
114
+
115
+ # ์ €์žฅ๋œ ํฌ์ธํŠธ ์ฐ๊ธฐ
116
+ df_points = df_points.dropna(subset=["lat", "lon"])
117
+ if len(df_points) > 0:
118
+ gdf_pts = gpd.GeoDataFrame(
119
+ df_points.copy(),
120
+ geometry=gpd.points_from_xy(df_points["lon"], df_points["lat"]),
121
+ crs=4326,
122
+ ).to_crs(epsg=3857)
123
+
124
+ gdf_pts.plot(
125
+ ax=ax,
126
+ column="dB",
127
+ cmap="viridis",
128
+ markersize=40,
129
+ edgecolor="black",
130
+ linewidth=0.3,
131
+ alpha=0.9,
132
+ legend=False,
133
+ zorder=5,
134
+ )
135
+
136
+ # ํด๋ฆฌ๊ณค bounds์— ๋”ฑ ๋งž๊ฒŒ
137
+ ax.set_xlim(BOUND_MINX, BOUND_MAXX)
138
+ ax.set_ylim(BOUND_MINY, BOUND_MAXY)
139
+ ax.axis("off")
140
+
141
+ # ์—ฌ๋ฐฑ ์ œ๊ฑฐํ•ด์„œ ์ „์ฒด ์บ”๋ฒ„์Šค๋ฅผ ์ง€๋„์— ๋งž์ถค
142
+ plt.subplots_adjust(0, 0, 1, 1)
143
+
144
+ # Figure -> PNG bytes -> PIL -> numpy
145
+ buf = io.BytesIO()
146
+ fig.savefig(buf, format="png", dpi=100)
147
+ buf.seek(0)
148
+ img = np.array(Image.open(buf))
149
+
150
+ plt.close(fig)
151
+ return img
152
+
153
+
154
+ # -------------------- ํ”ฝ์…€ โ†’ ์œ„๊ฒฝ๋„ ๋ณ€ํ™˜ --------------------
155
+ def pixel_to_latlon(x_px: int, y_px: int):
156
+ """
157
+ Gradio ์ด๋ฏธ์ง€ ํด๋ฆญ ์ขŒํ‘œ(ํ”ฝ์…€, (x, y))๋ฅผ
158
+ EPSG:3857 โ†’ EPSG:4326(lat, lon)์œผ๋กœ ๋ณ€ํ™˜.
159
+ """
160
+ # x: [0, IMG_W] -> [BOUND_MINX, BOUND_MAXX]
161
+ x_3857 = BOUND_MINX + (BOUND_MAXX - BOUND_MINX) * (x_px / IMG_W)
162
+ # y: ์œ„๊ฐ€ 0, ์•„๋ž˜๊ฐ€ IMG_H โ†’ [BOUND_MAXY, BOUND_MINY]๋กœ ๋งคํ•‘
163
+ y_3857 = BOUND_MAXY - (BOUND_MAXY - BOUND_MINY) * (y_px / IMG_H)
164
+
165
+ g = gpd.GeoSeries([Point(x_3857, y_3857)], crs=3857).to_crs(epsg=4326)
166
+ pt = g.iloc[0]
167
+ lat, lon = pt.y, pt.x
168
+
169
+ # ์—ฌ๊ธฐ์„œ ์†Œ์ˆ˜์  7์ž๋ฆฌ๋กœ ๋ฐ˜์˜ฌ๋ฆผ
170
+ return round(float(lat), 7), round(float(lon), 7)
171
+
172
+
173
+ # -------------------- Gradio ์ฝœ๋ฐฑ --------------------
174
+ def on_map_click(img, df_state, evt: gr.SelectData):
175
+ """์ด๋ฏธ์ง€ ํด๋ฆญ ์‹œ ์œ„๊ฒฝ๋„ ํ…์ŠคํŠธ ๋ฐ•์Šค ๊ฐฑ์‹ ."""
176
+ if evt is None or evt.index is None:
177
+ return None, None
178
+ x, y = evt.index # (x_px, y_px)
179
+ lat, lon = pixel_to_latlon(x, y)
180
+ return lat, lon # ์ด๋ฏธ 7์ž๋ฆฌ๋กœ ๋ฐ˜์˜ฌ๋ฆผ๋œ ๊ฐ’
181
+
182
+
183
+ def add_point(lat, lon, db, df_state):
184
+ """lat/lon/dB ์ถ”๊ฐ€ํ•˜๊ณ  CSV ์ €์žฅ + ์ง€๋„ ๊ฐฑ์‹ ."""
185
+ if df_state is None or isinstance(df_state, dict):
186
+ df_state = df_init.copy()
187
+
188
+ if lat is None or lon is None or db is None:
189
+ msg = "์œ„๋„/๊ฒฝ๋„/dB ์ž…๋ ฅ ์˜ค๋ฅ˜."
190
+ return df_state, make_map_image(df_state), df_state, msg
191
+
192
+ try:
193
+ lat = float(lat)
194
+ lon = float(lon)
195
+ db = float(db)
196
+ except ValueError:
197
+ msg = "์ˆซ์ž ์˜ค๋ฅ˜."
198
+ return df_state, make_map_image(df_state), df_state, msg
199
+
200
+ # ์•ˆ์ „ํ•˜๊ฒŒ ํ•œ ๋ฒˆ ๋” 7์ž๋ฆฌ๋กœ ๋งž์ถค
201
+ lat = round(lat, 7)
202
+ lon = round(lon, 7)
203
+
204
+ # ํด๋ฆฌ๊ณค ์•ˆ์ธ์ง€ ์ฒดํฌ
205
+ pt = gpd.GeoSeries([Point(lon, lat)], crs=4326).to_crs(epsg=3857).iloc[0]
206
+ if not poly_3857.contains(pt):
207
+ msg = "์บ ํผ์Šค ๊ฒฝ๊ณ„ ๋ฐ–."
208
+ return df_state, make_map_image(df_state), df_state, msg
209
+
210
+ new_row = pd.DataFrame([{"lat": lat, "lon": lon, "dB": db}])
211
+ new_df = pd.concat([df_state, new_row], ignore_index=True)
212
+
213
+ os.makedirs(os.path.dirname(CSV_PATH), exist_ok=True)
214
+ new_df[["lat", "lon", "dB"]].to_csv(CSV_PATH, index=False, encoding="cp949")
215
+
216
+ # ๋ฉ”์‹œ์ง€๋„ 7์ž๋ฆฌ๋กœ
217
+ msg = f"{len(new_df)}๊ฐœ ์  ์ €์žฅ๋จ (๋งˆ์ง€๋ง‰: lat={lat:.7f}, lon={lon:.7f}, dB={db:.1f})"
218
+ return new_df, make_map_image(new_df), new_df, msg
219
+
220
+ def load_uploaded_csv(file, df_state):
221
+ """
222
+ CSV ์—…๋กœ๋“œ ์‹œ:
223
+ - (์ผ€์ด์Šค A) '์œ„์น˜', 'dB' ์ปฌ๋Ÿผ์ด ์žˆ์œผ๋ฉด: ์œ„์น˜(WKT) -> lat/lon, dB ๊ทธ๋Œ€๋กœ
224
+ - (์ผ€์ด์Šค B) 'lat', 'lon', 'dB' ์ปฌ๋Ÿผ์ด ์žˆ์œผ๋ฉด: ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ
225
+ - ๋‘˜ ๋‹ค ์•„๋‹ˆ๋ฉด ์—๋Ÿฌ
226
+ """
227
+ if df_state is None or isinstance(df_state, dict):
228
+ df_state = df_init.copy()
229
+
230
+ if file is None:
231
+ msg = "์—…๋กœ๋“œ๋œ ํŒŒ์ผ ์—†์Œ."
232
+ return df_state, make_map_image(df_state), df_state, msg
233
+
234
+ # CSV ์ฝ๊ธฐ (์ธ์ฝ”๋”ฉ ์‹œ๋„)
235
+ try:
236
+ df_u = pd.read_csv(file.name, encoding="cp949")
237
+ except UnicodeDecodeError:
238
+ df_u = pd.read_csv(file.name, encoding="utf-8")
239
+
240
+ # ์ปฌ๋Ÿผ ์ด๋ฆ„ ๊ณต๋ฐฑ/Unnamed ์ •๋ฆฌ
241
+ df_u.columns = [c.strip() for c in df_u.columns]
242
+ df_u = df_u.loc[:, ~df_u.columns.str.contains("^Unnamed")]
243
+
244
+ # ---------- ์ผ€์ด์Šค B: lat/lon/dB ํ˜•์‹ ----------
245
+ if {"lat", "lon", "dB"}.issubset(df_u.columns):
246
+ new_df = df_u[["lat", "lon", "dB"]].copy()
247
+ # ์ˆซ์ž/๋ฐ˜์˜ฌ๋ฆผ ๋ณด์ •
248
+ new_df["lat"] = pd.to_numeric(new_df["lat"], errors="coerce").round(7)
249
+ new_df["lon"] = pd.to_numeric(new_df["lon"], errors="coerce").round(7)
250
+ new_df["dB"] = pd.to_numeric(new_df["dB"], errors="coerce")
251
+ new_df = new_df.dropna(subset=["lat", "lon"])
252
+
253
+ df_merged = new_df.copy()
254
+
255
+ os.makedirs(os.path.dirname(CSV_PATH), exist_ok=True)
256
+ df_merged[["lat", "lon", "dB"]].to_csv(CSV_PATH, index=False, encoding="cp949")
257
+
258
+ msg = f"lat/lon/dB ํ˜•์‹ CSV์—์„œ {len(df_merged)}๊ฐœ ์  ์ฝ์–ด์˜ด."
259
+ return df_merged, make_map_image(df_merged), df_merged, msg
260
+
261
+ # ---------- ์ผ€์ด์Šค A: ์œ„์น˜(WKT), dB ํ˜•์‹ ----------
262
+ if "์œ„์น˜" in df_u.columns and "dB" in df_u.columns:
263
+ pos_series = df_u["์œ„์น˜"].astype(str).str.strip()
264
+
265
+ def safe_load_wkt(s):
266
+ s_up = s.upper()
267
+ if "POINT" not in s_up:
268
+ return None
269
+ try:
270
+ return shp_wkt.loads(s)
271
+ except Exception:
272
+ return None
273
+
274
+ geom = pos_series.apply(safe_load_wkt)
275
+ mask = geom.notnull()
276
+
277
+ if not mask.any():
278
+ msg = "'์œ„์น˜' ์ปฌ๋Ÿผ์— ์œ ํšจํ•œ POINT WKT๊ฐ€ ์—†๋‹ค."
279
+ return df_state, make_map_image(df_state), df_state, msg
280
+
281
+ geom_valid = geom[mask]
282
+ lons = geom_valid.apply(lambda g: round(g.x, 7))
283
+ lats = geom_valid.apply(lambda g: round(g.y, 7))
284
+ dB_vals = pd.to_numeric(df_u.loc[mask, "dB"], errors="coerce")
285
+
286
+ new_df = pd.DataFrame(
287
+ {
288
+ "lat": lats.values,
289
+ "lon": lons.values,
290
+ "dB": dB_vals.values,
291
+ }
292
+ ).dropna(subset=["lat", "lon"])
293
+
294
+ df_merged = new_df.copy()
295
+
296
+ os.makedirs(os.path.dirname(CSV_PATH), exist_ok=True)
297
+ df_merged[["lat", "lon", "dB"]].to_csv(CSV_PATH, index=False, encoding="cp949")
298
+
299
+ msg = f"[์œ„์น˜, dB] ํ˜•์‹ CSV์—์„œ ์œ ํšจํ•œ ์  {len(df_merged)}๊ฐœ ์ฝ์–ด์˜ด."
300
+ return df_merged, make_map_image(df_merged), df_merged, msg
301
+
302
+ # ---------- ๋‘˜ ๋‹ค ์•„๋‹ˆ๋ฉด ----------
303
+ msg = "CSV ํ˜•์‹์„ ์•Œ์•„๋ณผ ์ˆ˜ ์—†๋‹ค. (์œ„์น˜,dB ๋˜๋Š” lat,lon,dB ํ•„์š”)"
304
+ return df_state, make_map_image(df_state), df_state, msg
305
+
306
+
307
+
308
+ # -------------------- Gradio ์•ฑ --------------------
309
+ def build_app():
310
+ init_img = make_map_image(df_init)
311
+
312
+ with gr.Blocks(title="์†Œ์Œ ์ธก์ • ๊ธฐ๋ก๊ธฐ") as demo:
313
+ gr.Markdown(
314
+ "## ์ธํ•˜๋Œ€ ์บ ํผ์Šค ์†Œ์Œ ์ธก์ •\n"
315
+ "- ์ง€๋„ ์ด๋ฏธ์ง€ ํด๋ฆญ โ†’ ์œ„/๊ฒฝ๋„ ์ž๋™ ์ž…๋ ฅ โ†’ dB ์ €์žฅ\n"
316
+ "- ๋˜๋Š” [์œ„์น˜, dB] CSV ์—…๋กœ๋“œํ•ด์„œ ํ•œ ๋ฒˆ์— ๋ณด์—ฌ์ฃผ๊ธฐ"
317
+ )
318
+
319
+ df_state = gr.State(df_init)
320
+
321
+ with gr.Row():
322
+ map_img = gr.Image(
323
+ value=init_img,
324
+ type="numpy",
325
+ label=None,
326
+ interactive=True,
327
+ )
328
+
329
+ with gr.Column():
330
+ lat_box = gr.Number(label="์œ„๋„ (lat)", interactive=False)
331
+ lon_box = gr.Number(label="๊ฒฝ๋„ (lon)", interactive=False)
332
+ db_box = gr.Number(label="๋ฐ์‹œ๋ฒจ (dB)")
333
+
334
+ add_btn = gr.Button("ํ˜„์žฌ ์ขŒํ‘œ ์ถ”๊ฐ€ + CSV ์ €์žฅ", variant="primary")
335
+ csv_upload = gr.File(
336
+ label="CSV ์—…๋กœ๋“œ ([์œ„์น˜, dB] ํ˜•์‹)",
337
+ file_types=[".csv"],
338
+ )
339
+
340
+ status = gr.Markdown()
341
+
342
+ table = gr.Dataframe(
343
+ value=df_init,
344
+ headers=["lat", "lon", "dB"],
345
+ label="์ €์žฅ๋œ ์ธก์ •๊ฐ’",
346
+ interactive=False,
347
+ wrap=True,
348
+ )
349
+
350
+ # ์ด๋ฏธ์ง€ ํด๋ฆญ ์‹œ: lat/lon ๊ฐฑ์‹ 
351
+ map_img.select(
352
+ fn=on_map_click,
353
+ inputs=[map_img, df_state],
354
+ outputs=[lat_box, lon_box],
355
+ )
356
+
357
+ # ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ: DF/์ง€๋„/์ƒํƒœ ๊ฐฑ์‹  + CSV ์ €์žฅ
358
+ add_btn.click(
359
+ fn=add_point,
360
+ inputs=[lat_box, lon_box, db_box, df_state],
361
+ outputs=[table, map_img, df_state, status],
362
+ )
363
+
364
+ # CSV ์—…๋กœ๋“œ ์‹œ: ์—…๋กœ๋“œ ๋ฐ์ดํ„ฐ๋กœ ์ง€๋„/ํ…Œ์ด๋ธ”/์ƒํƒœ ๊ฐฑ์‹ 
365
+ csv_upload.change(
366
+ fn=load_uploaded_csv,
367
+ inputs=[csv_upload, df_state],
368
+ outputs=[table, map_img, df_state, status],
369
+ )
370
+
371
+ return demo
372
+
373
+
374
+ if __name__ == "__main__":
375
+ app = build_app()
376
+ app.launch(share=True)
simul.py ADDED
@@ -0,0 +1,539 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # sampling_sim_app.py
2
+ import os
3
+ import io
4
+
5
+ import matplotlib
6
+ matplotlib.use("Agg")
7
+
8
+ import numpy as np
9
+ import pandas as pd
10
+ import geopandas as gpd
11
+ import matplotlib.pyplot as plt
12
+ from shapely.geometry import Point
13
+ from shapely import wkt as shp_wkt
14
+ from PIL import Image
15
+ import contextily as ctx # โ† ์ถ”๊ฐ€
16
+
17
+ import gradio as gr
18
+
19
+ # ----------------- ๊ฒฝ๋กœ ์„ค์ • -----------------
20
+ BASE_DIR = os.path.dirname(__file__)
21
+ DATA_DIR = os.path.join(BASE_DIR, "Data")
22
+
23
+ SHP_PATH = os.path.join(DATA_DIR, "inha_boundary.shp")
24
+ ZONES_PATH = os.path.join(DATA_DIR, "zones_์ตœ์ข….geojson")
25
+
26
+ # ์ธก์ •๊ฒฐ๊ณผ CSV (lat, lon, dB ๋˜๋Š” ์œ„์น˜, dB)
27
+ CSV_PATH = os.path.join(DATA_DIR, "์ธก์ •๊ฒฐ๊ณผ_์ตœ์ข….csv")
28
+
29
+
30
+ # ---------- ์œ ํ‹ธ: Figure -> numpy ----------
31
+ def fig_to_array(fig, dpi=100):
32
+ buf = io.BytesIO()
33
+ fig.savefig(buf, format="png", dpi=dpi, bbox_inches="tight")
34
+ buf.seek(0)
35
+ img = np.array(Image.open(buf))
36
+ plt.close(fig)
37
+ return img
38
+
39
+
40
+ # ---------- ๊ฒฝ๊ณ„ SHP ๋กœ๋”ฉ ----------
41
+ def load_boundary():
42
+ gdf4326 = gpd.read_file(SHP_PATH).to_crs(epsg=4326)
43
+ gdf3857 = gdf4326.to_crs(epsg=3857)
44
+
45
+ poly3857 = gdf3857.geometry.unary_union
46
+ if poly3857.geom_type == "MultiPolygon":
47
+ poly3857 = max(poly3857.geoms, key=lambda g: g.area)
48
+
49
+ return gdf4326, gdf3857, poly3857
50
+
51
+
52
+ # ---------- ์ธก์ •๊ฒฐ๊ณผ CSV ๋กœ๋”ฉ ----------
53
+ def load_points_from_csv():
54
+ try:
55
+ df_u = pd.read_csv(CSV_PATH, encoding="cp949")
56
+ except UnicodeDecodeError:
57
+ df_u = pd.read_csv(CSV_PATH, encoding="utf-8")
58
+
59
+ df_u.columns = [c.strip() for c in df_u.columns]
60
+ df_u = df_u.loc[:, ~df_u.columns.str.contains("^Unnamed")]
61
+
62
+ # ์ผ€์ด์Šค B: ์œ„์น˜, dB
63
+ if "์œ„์น˜" in df_u.columns and "dB" in df_u.columns:
64
+ pos_series = df_u["์œ„์น˜"].astype(str).str.strip()
65
+
66
+ def safe_load_wkt(s):
67
+ s_up = s.upper()
68
+ if "POINT" not in s_up:
69
+ return None
70
+ try:
71
+ return shp_wkt.loads(s)
72
+ except Exception:
73
+ return None
74
+
75
+ geom = pos_series.apply(safe_load_wkt)
76
+ mask = geom.notnull()
77
+ if not mask.any():
78
+ raise ValueError("'์œ„์น˜' ์ปฌ๋Ÿผ์— ์œ ํšจํ•œ POINT WKT๊ฐ€ ์—†๋‹ค.")
79
+
80
+ geom_valid = geom[mask]
81
+ lons = geom_valid.apply(lambda g: round(g.x, 7))
82
+ lats = geom_valid.apply(lambda g: round(g.y, 7))
83
+ dBs = pd.to_numeric(df_u.loc[mask, "dB"], errors="coerce")
84
+
85
+ df = pd.DataFrame({"lat": lats, "lon": lons, "dB": dBs}).dropna(
86
+ subset=["lat", "lon"]
87
+ )
88
+ return df
89
+
90
+ # ์ผ€์ด์Šค A: lat, lon, dB
91
+ if {"lat", "lon", "dB"}.issubset(df_u.columns):
92
+ df = df_u[["lat", "lon", "dB"]].copy()
93
+ df["lat"] = pd.to_numeric(df["lat"], errors="coerce").round(7)
94
+ df["lon"] = pd.to_numeric(df["lon"], errors="coerce").round(7)
95
+ df["dB"] = pd.to_numeric(df["dB"], errors="coerce")
96
+ df = df.dropna(subset=["lat", "lon"])
97
+ return df
98
+
99
+ raise ValueError("CSV๋Š” (lat, lon, dB) ๋˜๋Š” (์œ„์น˜, dB) ํ˜•์‹์ด์–ด์•ผ ํ•œ๋‹ค.")
100
+
101
+
102
+ # ---------- ๊ตฌ์—ญ GeoJSON ๋กœ๋”ฉ ----------
103
+ def load_zones(poly3857):
104
+ zones4326 = gpd.read_file(ZONES_PATH).to_crs(epsg=4326)
105
+ zones3857 = zones4326.to_crs(epsg=3857)
106
+
107
+ if "zone_id" not in zones3857.columns:
108
+ zones3857["zone_id"] = np.arange(1, len(zones3857) + 1)
109
+
110
+ zones3857["geom_clip"] = zones3857.geometry.intersection(poly3857)
111
+ zones3857["area"] = zones3857["geom_clip"].area
112
+ zones3857 = zones3857[zones3857["area"] > 0].copy()
113
+
114
+ A_total = zones3857["area"].sum()
115
+ zones3857["W_area"] = zones3857["area"] / A_total
116
+
117
+ return zones4326, zones3857
118
+
119
+
120
+ # ---------- ํฌ์ธํŠธ์— zone_id ๋ถ™์ด๊ธฐ ----------
121
+ def attach_zone_id(points_df, zones3857):
122
+ pts4326 = gpd.GeoDataFrame(
123
+ points_df.copy(),
124
+ geometry=gpd.points_from_xy(points_df["lon"], points_df["lat"]),
125
+ crs=4326,
126
+ )
127
+ pts3857 = pts4326.to_crs(epsg=3857)
128
+
129
+ zones_clip = zones3857[["zone_id", "geom_clip"]].copy()
130
+ zones_clip = zones_clip.set_geometry("geom_clip")
131
+ zones_clip = zones_clip.rename_geometry("geometry")
132
+
133
+ joined = gpd.sjoin(
134
+ pts3857,
135
+ zones_clip[["zone_id", "geometry"]],
136
+ how="inner",
137
+ predicate="within",
138
+ )
139
+
140
+ out = joined[["lat", "lon", "dB", "zone_id"]].copy().reset_index(drop=True)
141
+ return out
142
+
143
+
144
+ # ---------- ๊ณตํ†ต ์ค€๋น„: ๊ฒฝ๊ณ„/๊ตฌ์—ญ/ํฌ์ธํŠธ/์ธต์ •๋ณด ----------
145
+ def prepare_sampling_data():
146
+ boundary4326, boundary3857, poly3857 = load_boundary()
147
+ zones4326, zones3857 = load_zones(poly3857)
148
+ df_points = load_points_from_csv()
149
+ points_zone = attach_zone_id(df_points, zones3857)
150
+
151
+ # ์ธต๋ณ„ ๋ชจ์ง‘๋‹จ ํฌ๊ธฐ N_h
152
+ zone_sizes = points_zone.groupby("zone_id").size().rename("N_h")
153
+ zones_info = zones3857[["zone_id", "area", "W_area"]].merge(
154
+ zone_sizes, on="zone_id", how="left"
155
+ )
156
+ zones_info["N_h"] = zones_info["N_h"].fillna(0).astype(int)
157
+
158
+ # ์ธต๋ณ„ ํ‘œ์ค€ํŽธ์ฐจ S_h (๋„ค์ด๋งŒ ๋ฐฐ๋ถ„์šฉ)
159
+ strata_std = points_zone.groupby("zone_id")["dB"].std(ddof=1).rename("S_h")
160
+ zones_info = zones_info.merge(strata_std, on="zone_id", how="left")
161
+ zones_info["S_h"] = zones_info["S_h"].fillna(0.0)
162
+
163
+ return boundary4326, zones4326, df_points, points_zone, zones_info
164
+
165
+
166
+ # ---------- ๋ฉด์ ๋น„๋ก€ n_h ๊ณ„์‚ฐ ----------
167
+ def allocate_n_by_area(zones_info, n_total):
168
+ zones = zones_info.copy()
169
+ zones["n_raw"] = n_total * zones["W_area"]
170
+ zones["n"] = zones["n_raw"].round().astype(int)
171
+
172
+ diff = n_total - zones["n"].sum()
173
+ if diff != 0:
174
+ order = zones["n_raw"].sort_values(ascending=(diff < 0)).index
175
+ for idx in order[:abs(diff)]:
176
+ zones.loc[idx, "n"] += 1 if diff > 0 else -1
177
+
178
+ zones["n"] = zones[["n", "N_h"]].min(axis=1)
179
+ return zones
180
+
181
+
182
+ # ---------- ๋„ค์ด๋งŒ ๋ฐฐ๋ถ„ n_h ๊ณ„์‚ฐ ----------
183
+ def allocate_n_neyman(zones_info, n_total):
184
+ zones = zones_info.copy()
185
+ if "S_h" not in zones.columns:
186
+ raise ValueError("zones_info์— S_h๊ฐ€ ์—†๋‹ค. ๋„ค์ด๋งŒ ๋ฐฐ๋ถ„ ์ „์— S_h๋ฅผ ๊ณ„์‚ฐํ•ด์•ผ ํ•œ๋‹ค.")
187
+
188
+ zones["S_h"] = zones["S_h"].fillna(0.0)
189
+
190
+ # ๊ฐ€์ค‘์น˜ โˆ N_h * S_h
191
+ weight = zones["N_h"] * zones["S_h"]
192
+ if weight.sum() <= 0:
193
+ # ๋ถ„์‚ฐ์ด ์ „๋ถ€ 0์ด๋ฉด N_h ๋น„๋ก€๋กœ ๋ฐฐ๋ถ„
194
+ weight = zones["N_h"].copy()
195
+
196
+ zones["n_raw"] = n_total * weight / weight.sum()
197
+ zones["n"] = zones["n_raw"].round().astype(int)
198
+
199
+ diff = n_total - zones["n"].sum()
200
+ if diff != 0:
201
+ order = zones["n_raw"].sort_values(ascending=(diff < 0)).index
202
+ for idx in order[:abs(diff)]:
203
+ zones.loc[idx, "n"] += 1 if diff > 0 else -1
204
+
205
+ zones["n"] = zones[["n", "N_h"]].min(axis=1)
206
+ return zones
207
+
208
+
209
+ # ---------- ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ----------
210
+ def simulate_sampling(points_zone_df, zones_info, n, R, strat_label):
211
+ y_all = points_zone_df["dB"].to_numpy()
212
+ N = len(y_all)
213
+ mu_pop = float(np.mean(y_all))
214
+
215
+ zones = zones_info.set_index("zone_id")
216
+ W_h = zones["N_h"] / zones["N_h"].sum()
217
+ n_h = zones["n"]
218
+
219
+ idx_by_zone = {}
220
+ for z in zones.index:
221
+ idx_by_zone[z] = points_zone_df.index[
222
+ points_zone_df["zone_id"] == z
223
+ ].to_numpy()
224
+
225
+ srs_means = []
226
+ strat_means = []
227
+
228
+ for _ in range(R):
229
+ # SRS
230
+ n_eff = min(n, N)
231
+ idx_srs = np.random.choice(N, size=n_eff, replace=False)
232
+ mu_srs = float(np.mean(y_all[idx_srs]))
233
+ srs_means.append(mu_srs)
234
+
235
+ # Stratified
236
+ mu_str = 0.0
237
+ for z in zones.index:
238
+ N_h = int(zones.loc[z, "N_h"])
239
+ nh = int(n_h.loc[z])
240
+ if N_h == 0 or nh == 0:
241
+ continue
242
+ idx_pool = idx_by_zone[z]
243
+ if len(idx_pool) < nh:
244
+ nh = len(idx_pool)
245
+ choose = np.random.choice(idx_pool, size=nh, replace=False)
246
+ y_h = points_zone_df.loc[choose, "dB"].to_numpy()
247
+ ybar_h = float(np.mean(y_h))
248
+ mu_str += float(W_h.loc[z]) * ybar_h
249
+ strat_means.append(mu_str)
250
+
251
+ srs_means = np.array(srs_means)
252
+ strat_means = np.array(strat_means)
253
+
254
+ def metrics(arr, name):
255
+ mean_hat = float(arr.mean())
256
+ var_hat = float(arr.var(ddof=1))
257
+ bias = mean_hat - mu_pop
258
+ mse = float(((arr - mu_pop) ** 2).mean())
259
+ return {
260
+ "design": name,
261
+ "mean_hat": mean_hat,
262
+ "var_hat": var_hat,
263
+ "bias": bias,
264
+ "MSE": mse,
265
+ }
266
+
267
+ m_srs = metrics(srs_means, "SRS")
268
+ m_str = metrics(strat_means, strat_label)
269
+
270
+ df_metrics = pd.DataFrame([m_srs, m_str])
271
+ df_metrics["deff_vs_SRS"] = df_metrics["var_hat"] / df_metrics["var_hat"].iloc[0]
272
+
273
+ # ์ˆซ์ž 4์งธ ์ž๋ฆฌ๊นŒ์ง€ ๋ฐ˜์˜ฌ๋ฆผ
274
+ num_cols = ["mean_hat", "var_hat", "bias", "MSE", "deff_vs_SRS"]
275
+ df_metrics[num_cols] = df_metrics[num_cols].round(4)
276
+
277
+ return mu_pop, srs_means, strat_means, df_metrics
278
+
279
+
280
+ # ---------- ๊ตฌ์—ญ ์š”์•ฝ ํ…Œ์ด๋ธ” ----------
281
+ def build_zones_table(zones_alloc):
282
+ zones_table = zones_alloc[["zone_id", "area", "W_area", "N_h", "n"]].copy()
283
+ zones_table = zones_table.rename(
284
+ columns={
285
+ "area": "area_m2",
286
+ "W_area": "area_weight",
287
+ "N_h": "N_pop",
288
+ "n": "n_alloc",
289
+ }
290
+ )
291
+ zones_table["sampling_frac"] = zones_table["n_alloc"] / zones_table["N_pop"]
292
+
293
+ total_area_m2 = zones_table["area_m2"].sum()
294
+ total_area_weight = zones_table["area_weight"].sum()
295
+ total_N = zones_table["N_pop"].sum()
296
+ total_n = zones_table["n_alloc"].sum()
297
+ total_sampling_frac = total_n / total_N if total_N > 0 else np.nan
298
+
299
+ total_row = {
300
+ "zone_id": "๏ฟฝ๏ฟฝ๏ฟฝ๊ณ„",
301
+ "area_m2": total_area_m2,
302
+ "area_weight": total_area_weight,
303
+ "N_pop": total_N,
304
+ "n_alloc": total_n,
305
+ "sampling_frac": total_sampling_frac,
306
+ }
307
+
308
+ zones_table = zones_table[
309
+ ["zone_id", "area_m2", "area_weight", "N_pop", "n_alloc", "sampling_frac"]
310
+ ]
311
+ zones_table = pd.concat([zones_table, pd.DataFrame([total_row])], ignore_index=True)
312
+
313
+ # ์ˆซ์ž ์ปฌ๋Ÿผ 4์งธ ์ž๋ฆฌ๊นŒ์ง€ ๋ฐ˜์˜ฌ๋ฆผ
314
+ num_cols_zone = ["area_m2", "area_weight", "N_pop", "n_alloc", "sampling_frac"]
315
+ for col in num_cols_zone:
316
+ zones_table[col] = pd.to_numeric(zones_table[col], errors="coerce").round(4)
317
+
318
+ return zones_table, float(total_N), float(total_n)
319
+
320
+
321
+ # ---------- ์‹œ๊ฐํ™” ----------
322
+ def plot_population_map(boundary4326, zones4326, points_df):
323
+ g_boundary = boundary4326.to_crs(epsg=3857)
324
+ g_zones = zones4326.to_crs(epsg=3857)
325
+ g_pts = gpd.GeoDataFrame(
326
+ points_df.copy(),
327
+ geometry=gpd.points_from_xy(points_df["lon"], points_df["lat"]),
328
+ crs=4326,
329
+ ).to_crs(epsg=3857)
330
+
331
+ fig, ax = plt.subplots(figsize=(8, 8))
332
+
333
+ # ๋ฒ ์ด์Šค๋งต ๋จผ์ € ๊น”๊ธฐ
334
+ minx, miny, maxx, maxy = g_boundary.total_bounds
335
+ ax.set_xlim(minx, maxx)
336
+ ax.set_ylim(miny, maxy)
337
+ ctx.add_basemap(ax, source=ctx.providers.OpenStreetMap.Mapnik, alpha=0.7)
338
+
339
+ # ๊ทธ ์œ„์— ๊ฒฝ๊ณ„/๊ตฌ์—ญ/ํฌ์ธํŠธ
340
+ g_boundary.plot(ax=ax, facecolor="none", edgecolor="black", linewidth=2)
341
+ if len(g_zones) > 0:
342
+ g_zones.plot(
343
+ ax=ax,
344
+ facecolor="none",
345
+ edgecolor="red",
346
+ linewidth=1.5,
347
+ alpha=0.7,
348
+ )
349
+
350
+ g_pts.plot(
351
+ ax=ax,
352
+ column="dB",
353
+ cmap="viridis",
354
+ markersize=20,
355
+ edgecolor="black",
356
+ linewidth=0.2,
357
+ legend=False,
358
+ alpha=0.9,
359
+ )
360
+
361
+ ax.set_title("Population points with zones", fontsize=12)
362
+ ax.axis("off")
363
+ plt.tight_layout()
364
+ return fig_to_array(fig)
365
+
366
+
367
+ def plot_histograms(srs_means, strat_means, mu_pop, strat_label):
368
+ fig, ax = plt.subplots(figsize=(8, 5))
369
+
370
+ ax.hist(srs_means, bins=20, alpha=0.5, label="SRS")
371
+ ax.hist(strat_means, bins=20, alpha=0.5, label=strat_label)
372
+ ax.axvline(mu_pop, color="k", linestyle="--", label="Population mean")
373
+
374
+ ax.set_xlabel("Sample mean (dB)")
375
+ ax.set_ylabel("Frequency")
376
+ ax.legend()
377
+ ax.set_title("Sampling distribution of mean")
378
+ plt.tight_layout()
379
+ return fig_to_array(fig)
380
+
381
+
382
+ # ---------- Gradio ์ฝœ๋ฐฑ: ๋ฉด์ ๋น„๋ก€ ----------
383
+ def run_sim_area(n, R):
384
+ n = int(n)
385
+ R = int(R)
386
+
387
+ boundary4326, zones4326, df_points, points_zone, zones_info = prepare_sampling_data()
388
+
389
+ # ๋ฉด์ ๋น„๋ก€ ๋ฐฐ๋ถ„
390
+ zones_alloc = allocate_n_by_area(zones_info, n)
391
+ zones_table, total_N, total_n = build_zones_table(zones_alloc)
392
+
393
+ mu_pop, srs_means, strat_means, df_metrics = simulate_sampling(
394
+ points_zone, zones_alloc, n, R, "Stratified (area-proportional)"
395
+ )
396
+
397
+ map_img = plot_population_map(boundary4326, zones4326, df_points)
398
+ hist_img = plot_histograms(
399
+ srs_means, strat_means, mu_pop, "Stratified (area-proportional)"
400
+ )
401
+
402
+ summary = (
403
+ f"[๋ฉด์ ๋น„๋ก€ ์ธตํ™”]\n"
404
+ f"๋ชจ์ง‘๋‹จ ํฌ์ธํŠธ ์ˆ˜: {int(total_N)}๊ฐœ\n"
405
+ f"๊ตฌ์—ญ ์ˆ˜: {len(zones_alloc)}๊ฐœ\n"
406
+ f"๋ชจ์ง‘๋‹จ ํ‰๊ท  dB: {mu_pop:.4f}\n"
407
+ f"์ด ํ‘œ๋ณธํฌ๊ธฐ n = {int(total_n)} (์ž…๋ ฅ๊ฐ’ {n}), ๋ฐ˜๋ณต R = {R}"
408
+ )
409
+
410
+ return map_img, hist_img, df_metrics, zones_table, summary
411
+
412
+
413
+ # ---------- Gradio ์ฝœ๋ฐฑ: ๋„ค์ด๋งŒ ----------
414
+ def run_sim_neyman(n, R):
415
+ n = int(n)
416
+ R = int(R)
417
+
418
+ boundary4326, zones4326, df_points, points_zone, zones_info = prepare_sampling_data()
419
+
420
+ # ๋„ค์ด๋งŒ ๋ฐฐ๋ถ„
421
+ zones_alloc = allocate_n_neyman(zones_info, n)
422
+ zones_table, total_N, total_n = build_zones_table(zones_alloc)
423
+
424
+ mu_pop, srs_means, strat_means, df_metrics = simulate_sampling(
425
+ points_zone, zones_alloc, n, R, "Stratified (Neyman)"
426
+ )
427
+
428
+ map_img = plot_population_map(boundary4326, zones4326, df_points)
429
+ hist_img = plot_histograms(
430
+ srs_means, strat_means, mu_pop, "Stratified (Neyman)"
431
+ )
432
+
433
+ summary = (
434
+ f"[๋„ค์ด๋งŒ ์ธตํ™”]\n"
435
+ f"๋ชจ์ง‘๋‹จ ํฌ์ธํŠธ ์ˆ˜: {int(total_N)}๊ฐœ\n"
436
+ f"๊ตฌ์—ญ ์ˆ˜: {len(zones_alloc)}๊ฐœ\n"
437
+ f"๋ชจ์ง‘๋‹จ ํ‰๊ท  dB: {mu_pop:.4f}\n"
438
+ f"์ด ํ‘œ๋ณธํฌ๊ธฐ n = {int(total_n)} (์ž…๋ ฅ๊ฐ’ {n}), ๋ฐ˜๋ณต R = {R}"
439
+ )
440
+
441
+ return map_img, hist_img, df_metrics, zones_table, summary
442
+
443
+
444
+ # ---------- Gradio ์•ฑ ----------
445
+ def build_app():
446
+ with gr.Blocks(title="ํ‘œ๋ณธ์ถ”์ถœ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ (SRS vs ์ธตํ™”)") as demo:
447
+
448
+ with gr.Tab("SRS vs ๋ฉด์ ๋น„๋ก€ ์ธตํ™”"):
449
+ with gr.Row():
450
+ n_area = gr.Number(label="ํ‘œ๋ณธํฌ๊ธฐ n", value=60, precision=0)
451
+ R_area = gr.Number(label="์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ฐ˜๋ณต ํšŸ์ˆ˜ R", value=500, precision=0)
452
+
453
+ run_btn_area = gr.Button("๋ฉด์ ๋น„๋ก€ ์ธตํ™” ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์‹คํ–‰", variant="primary")
454
+
455
+ with gr.Row():
456
+ map_img_area = gr.Image(label="Population + zones")
457
+ hist_img_area = gr.Image(label="Sampling distribution")
458
+
459
+ metrics_table_area = gr.Dataframe(
460
+ headers=[
461
+ "design",
462
+ "mean_hat",
463
+ "var_hat",
464
+ "bias",
465
+ "MSE",
466
+ "deff_vs_SRS",
467
+ ],
468
+ label="์„ฑ๋Šฅ ๋น„๊ต ์ง€ํ‘œ",
469
+ interactive=False,
470
+ )
471
+
472
+ zones_table_area = gr.Dataframe(
473
+ label="๊ตฌ์—ญ๋ณ„ ๋ฉด์  ๋ฐ ๋ชจ์ง‘๋‹จ/๋ฐฐ์ • ํ‘œ๋ณธ์ˆ˜",
474
+ interactive=False,
475
+ )
476
+
477
+ summary_md_area = gr.Markdown()
478
+
479
+ run_btn_area.click(
480
+ fn=run_sim_area,
481
+ inputs=[n_area, R_area],
482
+ outputs=[
483
+ map_img_area,
484
+ hist_img_area,
485
+ metrics_table_area,
486
+ zones_table_area,
487
+ summary_md_area,
488
+ ],
489
+ )
490
+
491
+ with gr.Tab("SRS vs ๋„ค์ด๋งŒ ์ธตํ™”"):
492
+ with gr.Row():
493
+ n_neyman = gr.Number(label="ํ‘œ๋ณธํฌ๊ธฐ n", value=60, precision=0)
494
+ R_neyman = gr.Number(label="์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ฐ˜๋ณต ํšŸ์ˆ˜ R", value=500, precision=0)
495
+
496
+ run_btn_neyman = gr.Button("๋„ค์ด๋งŒ ์ธตํ™” ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์‹คํ–‰", variant="primary")
497
+
498
+ with gr.Row():
499
+ map_img_neyman = gr.Image(label="Population + zones")
500
+ hist_img_neyman = gr.Image(label="Sampling distribution")
501
+
502
+ metrics_table_neyman = gr.Dataframe(
503
+ headers=[
504
+ "design",
505
+ "mean_hat",
506
+ "var_hat",
507
+ "bias",
508
+ "MSE",
509
+ "deff_vs_SRS",
510
+ ],
511
+ label="์„ฑ๋Šฅ ๋น„๊ต ์ง€ํ‘œ",
512
+ interactive=False,
513
+ )
514
+
515
+ zones_table_neyman = gr.Dataframe(
516
+ label="๊ตฌ์—ญ๋ณ„ ๋ฉด์  ๋ฐ ๋ชจ์ง‘๋‹จ/๋ฐฐ์ • ํ‘œ๋ณธ์ˆ˜",
517
+ interactive=False,
518
+ )
519
+
520
+ summary_md_neyman = gr.Markdown()
521
+
522
+ run_btn_neyman.click(
523
+ fn=run_sim_neyman,
524
+ inputs=[n_neyman, R_neyman],
525
+ outputs=[
526
+ map_img_neyman,
527
+ hist_img_neyman,
528
+ metrics_table_neyman,
529
+ zones_table_neyman,
530
+ summary_md_neyman,
531
+ ],
532
+ )
533
+
534
+ return demo
535
+
536
+
537
+ if __name__ == "__main__":
538
+ app = build_app()
539
+ app.launch(share=True)
tset.ipynb ADDED
@@ -0,0 +1,1135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "9afdef8a",
6
+ "metadata": {},
7
+ "source": [
8
+ "# ๊ฒฝ๊ณ„์ •๋ณด ์ƒ์„ฑ"
9
+ ]
10
+ },
11
+ {
12
+ "cell_type": "code",
13
+ "execution_count": 18,
14
+ "id": "014473d8",
15
+ "metadata": {},
16
+ "outputs": [
17
+ {
18
+ "name": "stdout",
19
+ "output_type": "stream",
20
+ "text": [
21
+ "๐Ÿ” ์ธํ•˜๋Œ€ํ•™๊ต ๊ฒฝ๊ณ„ ์ •๋ณด ์ถ”์ถœ ์ค‘...\n",
22
+ "์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ: 504\n",
23
+ "โŒ API ์˜ค๋ฅ˜ ๋ฐœ์ƒ\n",
24
+ "๐Ÿ”„ ๋Œ€์ฒด ๋ฐฉ๋ฒ•์„ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค...\n",
25
+ "๐Ÿ“ Plan B: ์ขŒํ‘œ ๋ฒ”์œ„ ๊ฒ€์ƒ‰ ๋ฐฉ๋ฒ•...\n",
26
+ "โœ“ ๋Œ€์ฒด ๋ฐฉ๋ฒ• ์„ฑ๊ณต!\n",
27
+ "โœ“ 3๊ฐœ์˜ ์š”์†Œ ๋ฐœ๊ฒฌ\n",
28
+ "๐Ÿ” ๋ฐ›์€ ์š”์†Œ ๊ฐœ์ˆ˜: 3\n",
29
+ " [0] type: way, tags: {'amenity': 'university', 'building': 'yes', 'education': 'university', 'name': '๋‚˜๋นŒ๋ ˆ๊ด€', 'name:en': 'Student Club Hall'}\n",
30
+ " ์ขŒํ‘œ ๊ฐœ์ˆ˜: 13\n",
31
+ " ๋ฉด์ : 0.00000008\n",
32
+ " โœ“ ์ถ”๊ฐ€๋จ\n",
33
+ " [1] type: way, tags: {'amenity': 'university', 'education': 'university', 'name': '์ธํ•˜๋Œ€ํ•™๊ต', 'name:en': 'Inha University', 'name:ja': 'ไป่ทๅคงๅญฆๆ ก', 'name:zh': 'ไป่ทๅคงๅญฆ', 'wikidata': 'Q488851'}\n",
34
+ " ์ขŒํ‘œ ๊ฐœ์ˆ˜: 37\n",
35
+ " ๋ฉด์ : 0.00002906\n",
36
+ " โœ“ ์ถ”๊ฐ€๋จ\n",
37
+ " [2] type: way, tags: {'amenity': 'university', 'education': 'university', 'name': '์ธํ•˜๊ณต์—…์ „๋ฌธ๋Œ€ํ•™', 'name:en': 'Inha Technical College', 'name:ja': 'ไป่ทๅทฅๆฅญๅฐ‚้–€ๅคงๅญฆ', 'name:zh': 'ไป่ทๅทฅไธšไธ“้—จๅคงๅญฆ'}\n",
38
+ " ์ขŒํ‘œ ๊ฐœ์ˆ˜: 26\n",
39
+ " ๋ฉด์ : 0.00001058\n",
40
+ " โœ“ ์ถ”๊ฐ€๋จ\n",
41
+ "๐Ÿ“Š ์ด 3๊ฐœ feature ์ƒ์„ฑ\n",
42
+ " ๊ฐ€์žฅ ํฐ polygon ์„ ํƒ (๋ฉด์ : 0.00002906)\n",
43
+ "โœ“ GeoJSON ํŒŒ์ผ ์ €์žฅ ์™„๋ฃŒ: inha_boundary.geojson\n",
44
+ "โœ“ ์œ„์„ฑ ์ด๋ฏธ์ง€ (Esri) ์ถ”๊ฐ€ ์™„๋ฃŒ\n",
45
+ "โœ“ ์œ„์„ฑ ์ด๋ฏธ์ง€ ์ €์žฅ ์™„๋ฃŒ: inha_boundary_satellite.png\n",
46
+ "โœ“ ์ง€๋„ ์Šคํƒ€์ผ + ํด๋ฆฌํ•‘ ์ถ”๊ฐ€ ์™„๋ฃŒ\n"
47
+ ]
48
+ },
49
+ {
50
+ "name": "stderr",
51
+ "output_type": "stream",
52
+ "text": [
53
+ "C:\\Users\\USER\\AppData\\Local\\Temp\\ipykernel_4744\\1166856351.py:227: DeprecationWarning: The 'unary_union' attribute is deprecated, use the 'union_all()' method instead.\n",
54
+ " poly = gdf_web.geometry.unary_union\n"
55
+ ]
56
+ },
57
+ {
58
+ "name": "stdout",
59
+ "output_type": "stream",
60
+ "text": [
61
+ "โœ“ ์ง€๋„ ์ด๋ฏธ์ง€ ์ €์žฅ ์™„๋ฃŒ: inha_boundary_map.png\n",
62
+ "โœ“ ํด๋ฆฌ๊ณค ์ด๋ฏธ์ง€ ์ €์žฅ ์™„๋ฃŒ: inha_boundary_polygon.png\n",
63
+ "โœ“ Shapefile ์ €์žฅ ์™„๋ฃŒ: inha_boundary.shp\n",
64
+ "\n",
65
+ "๐Ÿ“ ๊ฒฝ๊ณ„ ์ •๋ณด:\n",
66
+ " - ์„œ์ชฝ: 126.649007, ๋‚จ์ชฝ: 37.446685\n",
67
+ " - ๋™์ชฝ: 126.657847, ๋ถ์ชฝ: 37.452137\n",
68
+ " - ์ค‘์‹ฌ์ : (126.653435, 37.449753)\n",
69
+ "\n",
70
+ "๐Ÿ“Œ ์ฒซ ๋ฒˆ์งธ ๊ฒฝ๊ณ„์˜ ์ขŒํ‘œ:\n",
71
+ " ์ด 37๊ฐœ์˜ ์ขŒํ‘œ์ \n",
72
+ " ์‹œ์ž‘์ : (126.6504913, 37.4488536)\n",
73
+ " ๋์ : (126.6504913, 37.4488536)\n"
74
+ ]
75
+ },
76
+ {
77
+ "name": "stderr",
78
+ "output_type": "stream",
79
+ "text": [
80
+ "c:\\Users\\USER\\anaconda3\\envs\\torch_ev\\lib\\site-packages\\pyogrio\\raw.py:723: RuntimeWarning: Normalized/laundered field name: 'name:en' to 'name_en'\n",
81
+ " ogr_write(\n",
82
+ "c:\\Users\\USER\\anaconda3\\envs\\torch_ev\\lib\\site-packages\\pyogrio\\raw.py:723: RuntimeWarning: Normalized/laundered field name: 'name:ja' to 'name_ja'\n",
83
+ " ogr_write(\n",
84
+ "c:\\Users\\USER\\anaconda3\\envs\\torch_ev\\lib\\site-packages\\pyogrio\\raw.py:723: RuntimeWarning: Normalized/laundered field name: 'name:zh' to 'name_zh'\n",
85
+ " ogr_write(\n"
86
+ ]
87
+ }
88
+ ],
89
+ "source": [
90
+ "import numpy as np\n",
91
+ "import matplotlib.path as mpath\n",
92
+ "import matplotlib.patches as mpatches\n",
93
+ "import requests\n",
94
+ "import json\n",
95
+ "import geopandas as gpd\n",
96
+ "from shapely.geometry import shape, Polygon, MultiPolygon\n",
97
+ "import matplotlib.pyplot as plt\n",
98
+ "import contextily as ctx\n",
99
+ "from matplotlib import font_manager\n",
100
+ "\n",
101
+ "# ๋Œ€์ฒด ๋ฐฉ๋ฒ•: ์ขŒํ‘œ ๋ฒ”์œ„๋กœ ๊ฒ€์ƒ‰\n",
102
+ "def get_inha_boundary_alternative():\n",
103
+ " print(\"๐Ÿ“ Plan B: ์ขŒํ‘œ ๋ฒ”์œ„ ๊ฒ€์ƒ‰ ๋ฐฉ๋ฒ•...\")\n",
104
+ " overpass_url = \"http://overpass-api.de/api/interpreter\"\n",
105
+ " \n",
106
+ " # ์ธํ•˜๋Œ€ํ•™๊ต ์ฃผ๋ณ€ ์˜์—ญ (์œ„๋„, ๊ฒฝ๋„ ๋ฒ”์œ„)\n",
107
+ " overpass_query = f\"\"\"\n",
108
+ " [out:json][timeout:25];\n",
109
+ " (\n",
110
+ " way[\"amenity\"=\"university\"](37.445,126.650,37.456,126.662);\n",
111
+ " relation[\"amenity\"=\"university\"](37.445,126.650,37.456,126.662);\n",
112
+ " way[\"name~\"=\"์ธํ•˜\"](37.445,126.650,37.456,126.662);\n",
113
+ " );\n",
114
+ " out geom;\n",
115
+ " \"\"\"\n",
116
+ " \n",
117
+ " try:\n",
118
+ " response = requests.post(overpass_url, data={'data': overpass_query}, timeout=30)\n",
119
+ " if response.status_code == 200:\n",
120
+ " data = response.json()\n",
121
+ " if data.get('elements'):\n",
122
+ " print(\"โœ“ ๋Œ€์ฒด ๋ฐฉ๋ฒ• ์„ฑ๊ณต!\")\n",
123
+ " return data\n",
124
+ " except Exception as e:\n",
125
+ " print(f\"โš ๏ธ ๋Œ€์ฒด ๋ฐฉ๋ฒ• ์‹คํŒจ: {e}\")\n",
126
+ " \n",
127
+ " print(\"๐Ÿ“ Plan C: ์ˆ˜๋™ ๊ฒฝ๊ณ„ ์ƒ์„ฑ...\")\n",
128
+ " return create_manual_boundary()\n",
129
+ "\n",
130
+ "# ์ˆ˜๋™์œผ๋กœ ๊ฒฝ๊ณ„ ์ƒ์„ฑ (์‹ค์ œ ์ธํ•˜๋Œ€ ๋ฉ”์ธ ์บ ํผ์Šค๋งŒ)\n",
131
+ "def create_manual_boundary():\n",
132
+ " print(\"โœ… ์ธํ•˜๋Œ€ํ•™๊ต ๋ฉ”์ธ ์บ ํผ์Šค ๊ฒฝ๊ณ„ ์ƒ์„ฑ\")\n",
133
+ " # ์ธํ•˜๋Œ€ํ•™๊ต ๋ฉ”์ธ ์บ ํผ์Šค ๊ฒฝ๊ณ„ (์ •๋ฌธ-5ํ˜ธ๊ด€-60์ฃผ๋…„๊ธฐ๋…๊ด€-๋ณธ๊ด€-์ฒด์œก๊ด€-ํ›„๋ฌธ ์ˆœ)\n",
134
+ " coords = [\n",
135
+ " [126.65168, 37.45100], # ์ •๋ฌธ\n",
136
+ " [126.65150, 37.45095],\n",
137
+ " [126.65140, 37.45020],\n",
138
+ " [126.65100, 37.44920],\n",
139
+ " [126.65090, 37.44880],\n",
140
+ " [126.65120, 37.44860],\n",
141
+ " [126.65280, 37.44780],\n",
142
+ " [126.65450, 37.44680],\n",
143
+ " [126.65580, 37.44680], # ๋‚จ๋™์ชฝ ๋ชจ์„œ๋ฆฌ\n",
144
+ " [126.65750, 37.44720],\n",
145
+ " [126.65850, 37.44780],\n",
146
+ " [126.65900, 37.44860],\n",
147
+ " [126.65920, 37.44980],\n",
148
+ " [126.65900, 37.45100], # ๋™์ชฝ ๊ฒฝ๊ณ„\n",
149
+ " [126.65850, 37.45180],\n",
150
+ " [126.65750, 37.45240],\n",
151
+ " [126.65620, 37.45260], # ๋ถ๋™์ชฝ ๋ชจ์„œ๋ฆฌ\n",
152
+ " [126.65500, 37.45250],\n",
153
+ " [126.65380, 37.45220],\n",
154
+ " [126.65250, 37.45180],\n",
155
+ " [126.65168, 37.45100], # ์ •๋ฌธ์œผ๋กœ ๋‹ซ๊ธฐ\n",
156
+ " ]\n",
157
+ " \n",
158
+ " return {\n",
159
+ " 'elements': [{\n",
160
+ " 'type': 'way',\n",
161
+ " 'geometry': [{'lon': c[0], 'lat': c[1]} for c in coords],\n",
162
+ " 'tags': {'name': '์ธํ•˜๋Œ€ํ•™๊ต', 'amenity': 'university'}\n",
163
+ " }]\n",
164
+ " }\n",
165
+ "\n",
166
+ "# OpenStreetMap Overpass API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ธํ•˜๋Œ€ํ•™๊ต ๊ฒฝ๊ณ„ ์ถ”์ถœ\n",
167
+ "def get_inha_boundary():\n",
168
+ " overpass_url = \"http://overpass-api.de/api/interpreter\"\n",
169
+ " \n",
170
+ " # ์ธํ•˜๋Œ€ํ•™๊ต๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋Š” Overpass ์ฟผ๋ฆฌ (๋” ์ •ํ™•ํ•œ ํ•„ํ„ฐ๋ง)\n",
171
+ " overpass_query = \"\"\"\n",
172
+ " [out:json][timeout:25];\n",
173
+ " (\n",
174
+ " way[\"name\"=\"์ธํ•˜๋Œ€ํ•™๊ต\"][\"amenity\"=\"university\"];\n",
175
+ " relation[\"name\"=\"์ธํ•˜๋Œ€ํ•™๊ต\"][\"amenity\"=\"university\"];\n",
176
+ " );\n",
177
+ " out geom;\n",
178
+ " \"\"\"\n",
179
+ " \n",
180
+ " try:\n",
181
+ " response = requests.get(overpass_url, params={'data': overpass_query}, timeout=30)\n",
182
+ " print(f\"์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ: {response.status_code}\")\n",
183
+ " \n",
184
+ " if response.status_code != 200:\n",
185
+ " print(f\"โŒ API ์˜ค๋ฅ˜ ๋ฐœ์ƒ\")\n",
186
+ " print(\"๐Ÿ”„ ๋Œ€์ฒด ๋ฐฉ๋ฒ•์„ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค...\")\n",
187
+ " return get_inha_boundary_alternative()\n",
188
+ " \n",
189
+ " data = response.json()\n",
190
+ " if not data.get('elements'):\n",
191
+ " print(\"โš ๏ธ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋Œ€์ฒด ๋ฐฉ๋ฒ•์„ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค...\")\n",
192
+ " return get_inha_boundary_alternative()\n",
193
+ " return data\n",
194
+ " except Exception as e:\n",
195
+ " print(f\"โŒ ์š”์ฒญ ์‹คํŒจ: {e}\")\n",
196
+ " print(\"๐Ÿ”„ ๋Œ€์ฒด ๋ฐฉ๋ฒ•์„ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค...\")\n",
197
+ " return get_inha_boundary_alternative()\n",
198
+ "\n",
199
+ "# GeoJSON ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜\n",
200
+ "def convert_to_geojson(osm_data):\n",
201
+ " features = []\n",
202
+ " \n",
203
+ " print(f\"๐Ÿ” ๋ฐ›์€ ์š”์†Œ ๊ฐœ์ˆ˜: {len(osm_data.get('elements', []))}\")\n",
204
+ " \n",
205
+ " for idx, element in enumerate(osm_data.get('elements', [])):\n",
206
+ " tags = element.get('tags', {})\n",
207
+ " print(f\" [{idx}] type: {element['type']}, tags: {tags}\")\n",
208
+ " \n",
209
+ " if element['type'] == 'way' and 'geometry' in element:\n",
210
+ " coords = [(node['lon'], node['lat']) for node in element['geometry']]\n",
211
+ " print(f\" ์ขŒํ‘œ ๊ฐœ์ˆ˜: {len(coords)}\")\n",
212
+ " \n",
213
+ " if len(coords) > 5: # ์ตœ์†Œ 5๊ฐœ ์ด์ƒ\n",
214
+ " try:\n",
215
+ " polygon = Polygon(coords)\n",
216
+ " area = polygon.area\n",
217
+ " print(f\" ๋ฉด์ : {area:.8f}\")\n",
218
+ " \n",
219
+ " if polygon.is_valid:\n",
220
+ " features.append({\n",
221
+ " 'type': 'Feature',\n",
222
+ " 'geometry': polygon.__geo_interface__,\n",
223
+ " 'properties': tags\n",
224
+ " })\n",
225
+ " print(f\" โœ“ ์ถ”๊ฐ€๋จ\")\n",
226
+ " except Exception as e:\n",
227
+ " print(f\" โœ— Polygon ์ƒ์„ฑ ์‹คํŒจ: {e}\")\n",
228
+ " \n",
229
+ " elif element['type'] == 'relation' and 'members' in element:\n",
230
+ " all_coords = []\n",
231
+ " for member in element['members']:\n",
232
+ " if 'geometry' in member:\n",
233
+ " coords = [(node['lon'], node['lat']) for node in member['geometry']]\n",
234
+ " all_coords.extend(coords)\n",
235
+ " \n",
236
+ " print(f\" relation ์ขŒํ‘œ ๊ฐœ์ˆ˜: {len(all_coords)}\")\n",
237
+ " \n",
238
+ " if len(all_coords) > 5:\n",
239
+ " try:\n",
240
+ " polygon = Polygon(all_coords)\n",
241
+ " if polygon.is_valid:\n",
242
+ " features.append({\n",
243
+ " 'type': 'Feature',\n",
244
+ " 'geometry': polygon.__geo_interface__,\n",
245
+ " 'properties': tags\n",
246
+ " })\n",
247
+ " print(f\" โœ“ relation ์ถ”๊ฐ€๋จ\")\n",
248
+ " except Exception as e:\n",
249
+ " print(f\" โœ— Relation polygon ์‹คํŒจ: {e}\")\n",
250
+ " \n",
251
+ " print(f\"๐Ÿ“Š ์ด {len(features)}๊ฐœ feature ์ƒ์„ฑ\")\n",
252
+ " \n",
253
+ " # ๋ฉด์ ์ด ๊ฐ€์žฅ ํฐ polygon ์„ ํƒ\n",
254
+ " if features and len(features) > 1:\n",
255
+ " features.sort(key=lambda f: Polygon(f['geometry']['coordinates'][0]).area, reverse=True)\n",
256
+ " print(f\" ๊ฐ€์žฅ ํฐ polygon ์„ ํƒ (๋ฉด์ : {Polygon(features[0]['geometry']['coordinates'][0]).area:.8f})\")\n",
257
+ " features = [features[0]]\n",
258
+ " \n",
259
+ " geojson = {\n",
260
+ " 'type': 'FeatureCollection',\n",
261
+ " 'features': features\n",
262
+ " }\n",
263
+ " \n",
264
+ " return geojson\n",
265
+ "\n",
266
+ "# ๊ฒฝ๊ณ„ ์‹œ๊ฐํ™” ๋ฐ ์ €์žฅ\n",
267
+ "def visualize_and_save(geojson_data, output_file='inha_boundary.geojson'):\n",
268
+ " # GeoJSON ํŒŒ์ผ๋กœ ์ €์žฅ\n",
269
+ " with open(output_file, 'w', encoding='utf-8') as f:\n",
270
+ " json.dump(geojson_data, f, ensure_ascii=False, indent=2)\n",
271
+ " print(f\"โœ“ GeoJSON ํŒŒ์ผ ์ €์žฅ ์™„๋ฃŒ: {output_file}\")\n",
272
+ " \n",
273
+ " # GeoDataFrame์œผ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์‹œ๊ฐํ™”\n",
274
+ " if geojson_data['features']:\n",
275
+ " gdf = gpd.GeoDataFrame.from_features(geojson_data['features'])\n",
276
+ " gdf.set_crs(epsg=4326, inplace=True)\n",
277
+ " \n",
278
+ " # Web Mercator ํˆฌ์˜๋ฒ•์œผ๋กœ ๋ณ€ํ™˜\n",
279
+ " gdf_web = gdf.to_crs(epsg=3857)\n",
280
+ " \n",
281
+ " # ํ•œ๊ธ€ ํฐํŠธ ์„ค์ •\n",
282
+ " try:\n",
283
+ " font_path = font_manager.findfont(font_manager.FontProperties(family='Malgun Gothic'))\n",
284
+ " plt.rcParams['font.family'] = 'Malgun Gothic'\n",
285
+ " except:\n",
286
+ " plt.rcParams['font.family'] = 'sans-serif'\n",
287
+ " \n",
288
+ " # ๋ฐฉ๋ฒ• 1: ์œ„์„ฑ ์ด๋ฏธ์ง€ (Esri WorldImagery - ์ตœ์‹ )\n",
289
+ " fig1, ax1 = plt.subplots(figsize=(15, 12))\n",
290
+ " gdf_web.plot(ax=ax1, facecolor='none', edgecolor='red', linewidth=3, alpha=0.9)\n",
291
+ " \n",
292
+ " try:\n",
293
+ " # ์ตœ์‹  ์œ„์„ฑ ์ด๋ฏธ์ง€ ์†Œ์Šค ์‹œ๋„\n",
294
+ " ctx.add_basemap(ax1, source=ctx.providers.Esri.WorldImagery, alpha=0.8)\n",
295
+ " print(\"โœ“ ์œ„์„ฑ ์ด๋ฏธ์ง€ (Esri) ์ถ”๊ฐ€ ์™„๋ฃŒ\")\n",
296
+ " except:\n",
297
+ " try:\n",
298
+ " # Google Satellite ๋Œ€์ฒด\n",
299
+ " ctx.add_basemap(ax1, source='https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', alpha=0.8)\n",
300
+ " print(\"โœ“ ์œ„์„ฑ ์ด๋ฏธ์ง€ (Google) ์ถ”๊ฐ€ ์™„๋ฃŒ\")\n",
301
+ " except Exception as e:\n",
302
+ " print(f\"โš ๏ธ ์œ„์„ฑ ์ด๋ฏธ์ง€ ์ถ”๊ฐ€ ์‹คํŒจ: {e}\")\n",
303
+ " \n",
304
+ " ax1.set_title('์ธํ•˜๋Œ€ํ•™๊ต ๊ฒฝ๊ณ„ (์œ„์„ฑ)', fontsize=18, pad=20, weight='bold')\n",
305
+ " ax1.axis('off')\n",
306
+ " plt.tight_layout()\n",
307
+ " plt.savefig('inha_boundary_satellite.png', dpi=300, bbox_inches='tight', facecolor='white')\n",
308
+ " print(\"โœ“ ์œ„์„ฑ ์ด๋ฏธ์ง€ ์ €์žฅ ์™„๋ฃŒ: inha_boundary_satellite.png\")\n",
309
+ " plt.close(fig1)\n",
310
+ " \n",
311
+ " # ๋ฐฉ๋ฒ• 2: ์ง€๋„ ์Šคํƒ€์ผ (OpenStreetMap)\n",
312
+ " # ๋ฐฉ๋ฒ• 2: ์ง€๋„ ์Šคํƒ€์ผ (OpenStreetMap) + ๊ฒฝ๊ณ„ ๋ฐ– ๋ฐฐ๊ฒฝ ํด๋ฆฌํ•‘\n",
313
+ " fig2, ax2 = plt.subplots(figsize=(15, 12))\n",
314
+ "\n",
315
+ " # Web Mercator ๊ธฐ์ค€ ํด๋ฆฌ๊ณค ์ค€๋น„ (ํ•ฉ์ณ์ง„ ํ•˜๋‚˜์˜ geometry)\n",
316
+ " poly = gdf_web.geometry.unary_union\n",
317
+ "\n",
318
+ " # MultiPolygon์ด๋ฉด ๊ฐ€์žฅ ํฐ ๊ฒƒ ํ•˜๋‚˜๋งŒ ์‚ฌ์šฉ (์„ ํƒ)\n",
319
+ " if poly.geom_type == \"MultiPolygon\":\n",
320
+ " poly = max(poly.geoms, key=lambda g: g.area)\n",
321
+ "\n",
322
+ " # ํด๋ฆฌ๊ณค์„ matplotlib PathPatch๋กœ ๋ณ€ํ™˜ (clip์šฉ)\n",
323
+ " x, y = poly.exterior.coords.xy\n",
324
+ " coords = np.vstack([x, y]).T\n",
325
+ "\n",
326
+ " Path = mpath.Path\n",
327
+ " path_data = [(Path.MOVETO, coords[0])]\n",
328
+ " for c in coords[1:]:\n",
329
+ " path_data.append((Path.LINETO, c))\n",
330
+ " path_data.append((Path.CLOSEPOLY, coords[0]))\n",
331
+ "\n",
332
+ " codes, verts = zip(*path_data)\n",
333
+ " clip_path = mpath.Path(verts, codes)\n",
334
+ " clip_patch = mpatches.PathPatch(clip_path, facecolor=\"none\", edgecolor=\"none\")\n",
335
+ "\n",
336
+ " ax2.add_patch(clip_patch)\n",
337
+ "\n",
338
+ " # ์šฐ์„  ํด๋ฆฌ๊ณค์„ ์ƒ‰์น \n",
339
+ " gdf_web.plot(ax=ax2, facecolor='lightblue', edgecolor='darkblue', linewidth=2.5, alpha=0.6)\n",
340
+ "\n",
341
+ " try:\n",
342
+ " ctx.add_basemap(ax2, source=ctx.providers.OpenStreetMap.Mapnik, alpha=0.7)\n",
343
+ " # ๋ฐฉ๊ธˆ ์ถ”๊ฐ€๋œ ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€์— ํด๋ฆฝ ์ ์šฉ\n",
344
+ " if ax2.images:\n",
345
+ " ax2.images[-1].set_clip_path(clip_patch)\n",
346
+ " print(\"โœ“ ์ง€๋„ ์Šคํƒ€์ผ + ํด๋ฆฌํ•‘ ์ถ”๊ฐ€ ์™„๋ฃŒ\")\n",
347
+ " except Exception as e:\n",
348
+ " print(f\"โš ๏ธ ์ง€๋„ ์ถ”๊ฐ€ ์‹คํŒจ: {e}\")\n",
349
+ " \n",
350
+ " ax2.set_title('์ธํ•˜๋Œ€ํ•™๊ต ๊ฒฝ๊ณ„ (์ง€๋„, boundary ๋ฐ– ๋ฐฐ๊ฒฝ ์ œ๊ฑฐ)', fontsize=18, pad=20, weight='bold')\n",
351
+ " ax2.axis('off')\n",
352
+ " plt.tight_layout()\n",
353
+ " plt.savefig('inha_boundary_map.png', dpi=300, bbox_inches='tight', facecolor='white')\n",
354
+ " print(\"โœ“ ์ง€๋„ ์ด๋ฏธ์ง€ ์ €์žฅ ์™„๋ฃŒ: inha_boundary_map.png\")\n",
355
+ " plt.close(fig2)\n",
356
+ "\n",
357
+ " \n",
358
+ " # ๋ฐฉ๋ฒ• 3: ๊ฒฝ๊ณ„๋งŒ (ํด๋ฆฌ๊ณค๋งŒ ๊ฐ•์กฐ)\n",
359
+ " fig3, ax3 = plt.subplots(figsize=(12, 10))\n",
360
+ " gdf.plot(ax=ax3, facecolor='#4CAF50', edgecolor='#1B5E20', linewidth=2.5, alpha=0.4)\n",
361
+ " ax3.set_title('์ธํ•˜๋Œ€ํ•™๊ต ์บ ํผ์Šค ์˜์—ญ', fontsize=18, pad=20, weight='bold')\n",
362
+ " ax3.set_xlabel('๊ฒฝ๋„ (Longitude)', fontsize=12)\n",
363
+ " ax3.set_ylabel('์œ„๋„ (Latitude)', fontsize=12)\n",
364
+ " ax3.grid(True, alpha=0.3, linestyle='--', linewidth=0.5)\n",
365
+ " ax3.set_facecolor('#f5f5f5')\n",
366
+ " plt.tight_layout()\n",
367
+ " plt.savefig('inha_boundary_polygon.png', dpi=300, bbox_inches='tight', facecolor='white')\n",
368
+ " print(\"โœ“ ํด๋ฆฌ๊ณค ์ด๋ฏธ์ง€ ์ €์žฅ ์™„๋ฃŒ: inha_boundary_polygon.png\")\n",
369
+ " plt.close(fig3)\n",
370
+ " \n",
371
+ " # Shapefile๋กœ๋„ ์ €์žฅ (GIS ์†Œํ”„ํŠธ์›จ์–ด์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ)\n",
372
+ " try:\n",
373
+ " gdf.to_file('inha_boundary.shp')\n",
374
+ " print(\"โœ“ Shapefile ์ €์žฅ ์™„๋ฃŒ: inha_boundary.shp\")\n",
375
+ " except Exception as e:\n",
376
+ " print(f\"โš ๏ธ Shapefile ์ €์žฅ ์‹คํŒจ: {e}\")\n",
377
+ " \n",
378
+ " # ๊ฒฝ๊ณ„ ์ •๋ณด ์ถœ๋ ฅ\n",
379
+ " print(f\"\\n๐Ÿ“ ๊ฒฝ๊ณ„ ์ •๋ณด:\")\n",
380
+ " bounds = gdf.total_bounds\n",
381
+ " print(f\" - ์„œ์ชฝ: {bounds[0]:.6f}, ๋‚จ์ชฝ: {bounds[1]:.6f}\")\n",
382
+ " print(f\" - ๋™์ชฝ: {bounds[2]:.6f}, ๋ถ์ชฝ: {bounds[3]:.6f}\")\n",
383
+ " \n",
384
+ " # CRS ๋ณ€ํ™˜ ํ›„ ์ค‘์‹ฌ์  ๊ณ„์‚ฐ (๊ฒฝ๊ณ  ๋ฐฉ์ง€)\n",
385
+ " gdf_projected = gdf.to_crs(epsg=5179) # ํ•œ๊ตญ ํ‰๋ฉด์ง๊ฐ์ขŒํ‘œ๊ณ„\n",
386
+ " centroid_proj = gdf_projected.centroid.iloc[0]\n",
387
+ " centroid_wgs84 = gpd.GeoSeries([centroid_proj], crs=5179).to_crs(4326).iloc[0]\n",
388
+ " print(f\" - ์ค‘์‹ฌ์ : ({centroid_wgs84.x:.6f}, {centroid_wgs84.y:.6f})\")\n",
389
+ " \n",
390
+ " return gdf\n",
391
+ " else:\n",
392
+ " print(\"โŒ ๊ฒฝ๊ณ„ ๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.\")\n",
393
+ " return None\n",
394
+ "\n",
395
+ "# ๋ฉ”์ธ ์‹คํ–‰\n",
396
+ "if __name__ == \"__main__\":\n",
397
+ " print(\"๐Ÿ” ์ธํ•˜๋Œ€ํ•™๊ต ๊ฒฝ๊ณ„ ์ •๋ณด ์ถ”์ถœ ์ค‘...\")\n",
398
+ " \n",
399
+ " # 1. OSM ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ\n",
400
+ " osm_data = get_inha_boundary()\n",
401
+ " print(f\"โœ“ {len(osm_data.get('elements', []))}๊ฐœ์˜ ์š”์†Œ ๋ฐœ๊ฒฌ\")\n",
402
+ " \n",
403
+ " # 2. GeoJSON์œผ๋กœ ๋ณ€ํ™˜\n",
404
+ " geojson = convert_to_geojson(osm_data)\n",
405
+ " \n",
406
+ " # 3. ์ €์žฅ ๋ฐ ์‹œ๊ฐํ™”\n",
407
+ " gdf = visualize_and_save(geojson)\n",
408
+ " \n",
409
+ " # 4. ์ขŒํ‘œ ๋ฐ์ดํ„ฐ ์ถœ๋ ฅ (ํ•„์š”์‹œ)\n",
410
+ " if geojson['features']:\n",
411
+ " print(\"\\n๐Ÿ“Œ ์ฒซ ๋ฒˆ์งธ ๊ฒฝ๊ณ„์˜ ์ขŒํ‘œ:\")\n",
412
+ " coords = geojson['features'][0]['geometry']['coordinates']\n",
413
+ " print(f\" ์ด {len(coords[0]) if coords else 0}๊ฐœ์˜ ์ขŒํ‘œ์ \")\n",
414
+ " if coords and len(coords[0]) > 0:\n",
415
+ " print(f\" ์‹œ์ž‘์ : {coords[0][0]}\")\n",
416
+ " print(f\" ๋์ : {coords[0][-1]}\")"
417
+ ]
418
+ },
419
+ {
420
+ "cell_type": "markdown",
421
+ "id": "79139ad6",
422
+ "metadata": {},
423
+ "source": [
424
+ "# ํŒŒ์ผ๋กœ ๏ฟฝ๏ฟฝ๋Ÿฌ์˜ค๊ธฐ"
425
+ ]
426
+ },
427
+ {
428
+ "cell_type": "code",
429
+ "execution_count": 21,
430
+ "id": "6f2d7463",
431
+ "metadata": {},
432
+ "outputs": [
433
+ {
434
+ "name": "stderr",
435
+ "output_type": "stream",
436
+ "text": [
437
+ "C:\\Users\\USER\\AppData\\Local\\Temp\\ipykernel_4744\\2019802498.py:19: DeprecationWarning: The 'unary_union' attribute is deprecated, use the 'union_all()' method instead.\n",
438
+ " poly = gdf_web.geometry.unary_union\n"
439
+ ]
440
+ },
441
+ {
442
+ "name": "stdout",
443
+ "output_type": "stream",
444
+ "text": [
445
+ "โœ“ ์ €์žฅ ์™„๋ฃŒ: inha_boundary_map_from_shp.png\n"
446
+ ]
447
+ }
448
+ ],
449
+ "source": [
450
+ "import geopandas as gpd\n",
451
+ "import matplotlib.pyplot as plt\n",
452
+ "import contextily as ctx\n",
453
+ "import numpy as np\n",
454
+ "import matplotlib.path as mpath\n",
455
+ "import matplotlib.patches as mpatches\n",
456
+ "\n",
457
+ "def plot_clipped_map_from_shp(\n",
458
+ " shp_path=\"inha_boundary.shp\",\n",
459
+ " out_png=\"inha_boundary_map_from_shp.png\"\n",
460
+ "):\n",
461
+ " # 1) Shapefile ์ฝ๊ธฐ (shx/dbf/prj๋Š” ๊ฐ™์ด ์ž๋™์œผ๋กœ ์ฝํž˜)\n",
462
+ " gdf = gpd.read_file(shp_path)\n",
463
+ "\n",
464
+ " # 2) Web Mercator๋กœ ๋ณ€ํ™˜ (contextily์šฉ)\n",
465
+ " gdf_web = gdf.to_crs(epsg=3857)\n",
466
+ "\n",
467
+ " # 3) ํด๋ฆฌ๊ณค ํ•˜๋‚˜๋กœ ํ•ฉ์น˜๊ธฐ (MultiPolygon์ด๋ฉด ๊ฐ€์žฅ ํฐ ๊ฒƒ ์„ ํƒ)\n",
468
+ " poly = gdf_web.geometry.unary_union\n",
469
+ " if poly.geom_type == \"MultiPolygon\":\n",
470
+ " poly = max(poly.geoms, key=lambda g: g.area)\n",
471
+ "\n",
472
+ " # 4) clip์— ์“ธ PathPatch ๋งŒ๋“ค๊ธฐ\n",
473
+ " x, y = poly.exterior.coords.xy\n",
474
+ " coords = np.vstack([x, y]).T\n",
475
+ "\n",
476
+ " Path = mpath.Path\n",
477
+ " path_data = [(Path.MOVETO, coords[0])]\n",
478
+ " for c in coords[1:]:\n",
479
+ " path_data.append((Path.LINETO, c))\n",
480
+ " path_data.append((Path.CLOSEPOLY, coords[0]))\n",
481
+ "\n",
482
+ " codes, verts = zip(*path_data)\n",
483
+ " clip_path = mpath.Path(verts, codes)\n",
484
+ " clip_patch = mpatches.PathPatch(clip_path, facecolor=\"none\", edgecolor=\"none\")\n",
485
+ "\n",
486
+ " # 5) ๊ทธ๋ฆผ ์ƒ์„ฑ\n",
487
+ " fig, ax = plt.subplots(figsize=(15, 12))\n",
488
+ "\n",
489
+ " # ํด๋ฆฌ๊ณค ๋จผ์ € ์ƒ‰์น  (๊ฒฝ๊ณ„ ํ™•์ธ์šฉ)\n",
490
+ " gdf_web.plot(ax=ax, facecolor=\"lightblue\", edgecolor=\"darkblue\", alpha=0.6, linewidth=2)\n",
491
+ "\n",
492
+ " # clip ํŒจ์น˜ ์ถ”๊ฐ€\n",
493
+ " ax.add_patch(clip_patch)\n",
494
+ "\n",
495
+ " # 6) basemap ์ถ”๊ฐ€ + ํด๋ฆฌํ•‘\n",
496
+ " ctx.add_basemap(ax, source=ctx.providers.OpenStreetMap.Mapnik, alpha=0.7)\n",
497
+ " if ax.images:\n",
498
+ " ax.images[-1].set_clip_path(clip_patch)\n",
499
+ "\n",
500
+ " # 7) ์บ ํผ์Šค ์˜์—ญ๋งŒ ๋ณด์ด๊ฒŒ ์ถ• ๋ฒ”์œ„๋„ ํด๋ฆฌ๊ณค์— ๋งž๊ฒŒ ์ž˜๋ผ์ฃผ๊ธฐ\n",
501
+ " minx, miny, maxx, maxy = poly.bounds\n",
502
+ " ax.set_xlim(minx, maxx)\n",
503
+ " ax.set_ylim(miny, maxy)\n",
504
+ " ax.axis(\"off\")\n",
505
+ " plt.tight_layout()\n",
506
+ " plt.savefig(out_png, dpi=300, bbox_inches=\"tight\", facecolor=\"white\")\n",
507
+ " plt.close(fig)\n",
508
+ "\n",
509
+ " print(f\"โœ“ ์ €์žฅ ์™„๋ฃŒ: {out_png}\")\n",
510
+ "\n",
511
+ "\n",
512
+ "plot_clipped_map_from_shp()\n"
513
+ ]
514
+ },
515
+ {
516
+ "cell_type": "markdown",
517
+ "id": "9ee6be64",
518
+ "metadata": {},
519
+ "source": [
520
+ "# ํ‘œ๋ณธ ์ถ”์ถœ ๋น„๊ต"
521
+ ]
522
+ },
523
+ {
524
+ "cell_type": "code",
525
+ "execution_count": 29,
526
+ "id": "c68604ed",
527
+ "metadata": {},
528
+ "outputs": [
529
+ {
530
+ "name": "stderr",
531
+ "output_type": "stream",
532
+ "text": [
533
+ "C:\\Users\\USER\\AppData\\Local\\Temp\\ipykernel_4744\\3417582304.py:15: DeprecationWarning: The 'unary_union' attribute is deprecated, use the 'union_all()' method instead.\n",
534
+ " poly = gdf.geometry.unary_union # ํ•˜๋‚˜์˜ ํด๋ฆฌ๊ณค์œผ๋กœ ํ•ฉ์น˜๊ธฐ\n"
535
+ ]
536
+ },
537
+ {
538
+ "name": "stdout",
539
+ "output_type": "stream",
540
+ "text": [
541
+ "๊ฒฉ์ž ์ธต๋ณ„ ํ‘œ๋ณธ ์ˆ˜ ํ• ๋‹น (๋ฉด์ ๋น„๋ก€): [ 4 12 7 12 16 9]\n",
542
+ "๋™์ผ๋ฉด์  ์ธต๋ณ„ ํ‘œ๋ณธ ์ˆ˜ ํ• ๋‹น (๋™์ผ ํ‘œ๋ณธ์ˆ˜): [10 10 10 10 10 10]\n"
543
+ ]
544
+ },
545
+ {
546
+ "name": "stderr",
547
+ "output_type": "stream",
548
+ "text": [
549
+ "C:\\Users\\USER\\AppData\\Local\\Temp\\ipykernel_4744\\3417582304.py:205: DeprecationWarning: The 'unary_union' attribute is deprecated, use the 'union_all()' method instead.\n",
550
+ " poly_web = gdf_web.geometry.unary_union\n"
551
+ ]
552
+ },
553
+ {
554
+ "name": "stdout",
555
+ "output_type": "stream",
556
+ "text": [
557
+ "โœ“ ์›๋ณธ ๊ฒฝ๊ณ„ ์ง€๋„ ์ €์žฅ: inha_boundary_base.png\n"
558
+ ]
559
+ },
560
+ {
561
+ "name": "stderr",
562
+ "output_type": "stream",
563
+ "text": [
564
+ "C:\\Users\\USER\\AppData\\Local\\Temp\\ipykernel_4744\\3417582304.py:205: DeprecationWarning: The 'unary_union' attribute is deprecated, use the 'union_all()' method instead.\n",
565
+ " poly_web = gdf_web.geometry.unary_union\n"
566
+ ]
567
+ },
568
+ {
569
+ "name": "stdout",
570
+ "output_type": "stream",
571
+ "text": [
572
+ "โœ“ SRS ์ƒ˜ํ”Œ ์ง€๋„ ์ €์žฅ: inha_sampling_srs.png\n"
573
+ ]
574
+ },
575
+ {
576
+ "name": "stderr",
577
+ "output_type": "stream",
578
+ "text": [
579
+ "C:\\Users\\USER\\AppData\\Local\\Temp\\ipykernel_4744\\3417582304.py:205: DeprecationWarning: The 'unary_union' attribute is deprecated, use the 'union_all()' method instead.\n",
580
+ " poly_web = gdf_web.geometry.unary_union\n"
581
+ ]
582
+ },
583
+ {
584
+ "name": "stdout",
585
+ "output_type": "stream",
586
+ "text": [
587
+ "โœ“ ์ธตํ™” ์ƒ˜ํ”Œ ์ง€๋„ ์ €์žฅ: inha_sampling_strata.png\n"
588
+ ]
589
+ },
590
+ {
591
+ "name": "stderr",
592
+ "output_type": "stream",
593
+ "text": [
594
+ "C:\\Users\\USER\\AppData\\Local\\Temp\\ipykernel_4744\\3417582304.py:205: DeprecationWarning: The 'unary_union' attribute is deprecated, use the 'union_all()' method instead.\n",
595
+ " poly_web = gdf_web.geometry.unary_union\n"
596
+ ]
597
+ },
598
+ {
599
+ "name": "stdout",
600
+ "output_type": "stream",
601
+ "text": [
602
+ "โœ“ ๋™์ผ๋ฉด์  ์ธตํ™” ์ƒ˜ํ”Œ ์ง€๋„ ์ €์žฅ: inha_sampling_strata_equalarea.png\n",
603
+ "\n",
604
+ "[SRS ์ƒ˜ํ”Œ ์ขŒํ‘œ (EPSG:5179)]\n",
605
+ " design geometry\n",
606
+ "0 SRS POINT (925278.467 1939423.964)\n",
607
+ "1 SRS POINT (925060.691 1939262.149)\n",
608
+ "2 SRS POINT (925344.356 1939270.6)\n",
609
+ "3 SRS POINT (925259.687 1939296.418)\n",
610
+ "4 SRS POINT (925386.793 1939152.443)\n",
611
+ "\n",
612
+ "[๊ฒฉ์ž ์ธตํ™” ์ƒ˜ํ”Œ ์ขŒํ‘œ (EPSG:5179)]\n",
613
+ " stratum_id geometry\n",
614
+ "0 0 POINT (924882.652 1939247.19)\n",
615
+ "1 0 POINT (924924.472 1939181.424)\n",
616
+ "2 0 POINT (924947.85 1939212.952)\n",
617
+ "3 0 POINT (924930.148 1939196.918)\n",
618
+ "4 1 POINT (925147.586 1939174.715)\n",
619
+ "\n",
620
+ "[๋™์ผ๋ฉด์  ์ธตํ™” ์ƒ˜ํ”Œ ์ขŒํ‘œ (EPSG:5179)]\n",
621
+ " stratum_id geometry\n",
622
+ "0 0 POINT (924846.854 1939231.61)\n",
623
+ "1 0 POINT (924830.9 1939503.537)\n",
624
+ "2 0 POINT (924832.812 1939290.495)\n",
625
+ "3 0 POINT (924882.953 1939275.119)\n",
626
+ "4 0 POINT (924776.295 1939433.993)\n"
627
+ ]
628
+ }
629
+ ],
630
+ "source": [
631
+ "import numpy as np\n",
632
+ "import geopandas as gpd\n",
633
+ "from shapely.geometry import Point, box\n",
634
+ "import matplotlib.pyplot as plt\n",
635
+ "import contextily as ctx\n",
636
+ "import matplotlib.path as mpath\n",
637
+ "import matplotlib.patches as mpatches\n",
638
+ "\n",
639
+ "# ===========================\n",
640
+ "# 1. ๊ณตํ†ต: ๊ฒฝ๊ณ„ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ\n",
641
+ "# ===========================\n",
642
+ "def load_inha_boundary(shp_path=\"inha_boundary.shp\"):\n",
643
+ " gdf = gpd.read_file(shp_path) # prj ๊ธฐ์ค€์œผ๋กœ CRS ์ธ์‹\n",
644
+ " gdf = gdf.to_crs(epsg=5179) # ํ•œ๊ตญ ํ‰๋ฉด์ง๊ฐ์ขŒํ‘œ๊ณ„\n",
645
+ " poly = gdf.geometry.unary_union # ํ•˜๋‚˜์˜ ํด๋ฆฌ๊ณค์œผ๋กœ ํ•ฉ์น˜๊ธฐ\n",
646
+ " return gdf, poly\n",
647
+ "\n",
648
+ "\n",
649
+ "# ===========================\n",
650
+ "# 2. ํด๋ฆฌ๊ณค ๋‚ด๋ถ€ ๋žœ๋ค ์ƒ˜ํ”Œ๋ง (SRS)\n",
651
+ "# ===========================\n",
652
+ "def random_points_in_polygon(poly, n):\n",
653
+ " minx, miny, maxx, maxy = poly.bounds\n",
654
+ " points = []\n",
655
+ " # rejection sampling: bounding box์—์„œ ๋ฝ‘์•„์„œ, ํด๋ฆฌ๊ณค ์•ˆ๋งŒ ์‚ฌ์šฉ\n",
656
+ " while len(points) < n:\n",
657
+ " xs = np.random.uniform(minx, maxx, n * 2)\n",
658
+ " ys = np.random.uniform(miny, maxy, n * 2)\n",
659
+ " for x, y in zip(xs, ys):\n",
660
+ " p = Point(x, y)\n",
661
+ " if poly.contains(p): # ๊ฒฝ๊ณ„๊นŒ์ง€ ํฌํ•จํ•˜๋ ค๋ฉด contains ๋Œ€์‹  covers ์จ๋„ ๋จ\n",
662
+ " points.append(p)\n",
663
+ " if len(points) == n:\n",
664
+ " break\n",
665
+ " return points\n",
666
+ "\n",
667
+ "\n",
668
+ "# ===========================\n",
669
+ "# 3-1. ํด๋ฆฌ๊ณค์„ ๊ฒฉ์ž๋กœ ๋‚˜๋ˆˆ ํ›„, ์ธต(๊ตฌ์—ญ) ๋งŒ๋“ค๊ธฐ (๊ธฐ์กด ๋ฐฉ์‹)\n",
670
+ "# ===========================\n",
671
+ "def make_strata(poly, n_rows=2, n_cols=3):\n",
672
+ " \"\"\"\n",
673
+ " n_rows x n_cols ๊ฒฉ์ž ์ƒ์„ฑ ํ›„,\n",
674
+ " ๊ฐ cell๊ณผ ์บ ํผ์Šค ํด๋ฆฌ๊ณค์˜ ๊ต์ฐจ(intersection)๋ฅผ strata๋กœ ์‚ฌ์šฉ\n",
675
+ " \"\"\"\n",
676
+ " minx, miny, maxx, maxy = poly.bounds\n",
677
+ " dx = (maxx - minx) / n_cols\n",
678
+ " dy = (maxy - miny) / n_rows\n",
679
+ "\n",
680
+ " strata_geoms = []\n",
681
+ " for i in range(n_rows):\n",
682
+ " for j in range(n_cols):\n",
683
+ " cell = box(\n",
684
+ " minx + j * dx,\n",
685
+ " miny + i * dy,\n",
686
+ " minx + (j + 1) * dx,\n",
687
+ " miny + (i + 1) * dy,\n",
688
+ " )\n",
689
+ " inter = cell.intersection(poly)\n",
690
+ " if not inter.is_empty and inter.area > 0:\n",
691
+ " strata_geoms.append(inter)\n",
692
+ "\n",
693
+ " strata_gdf = gpd.GeoDataFrame(\n",
694
+ " {\"stratum_id\": range(len(strata_geoms))},\n",
695
+ " geometry=strata_geoms,\n",
696
+ " crs=\"EPSG:5179\"\n",
697
+ " )\n",
698
+ " return strata_gdf\n",
699
+ "\n",
700
+ "\n",
701
+ "# ===========================\n",
702
+ "# 3-2. ๋™์ผ๋ฉด์ ์œผ๋กœ ๋‚˜๋ˆ„๋Š” ์ธต(๊ตฌ์—ญ) ๋งŒ๋“ค๊ธฐ\n",
703
+ "# - x์ถ• ๋ฐฉํ–ฅ์œผ๋กœ ์ž˜๋ผ์„œ ๊ฐ ๋ฐด๋“œ์˜ ํด๋ฆฌ๊ณค ๋ฉด์ ์ด ๊ฑฐ์˜ ๊ฐ™๊ฒŒ\n",
704
+ "# ===========================\n",
705
+ "# ===========================\n",
706
+ "# 3-2. ๋™์ผ๋ฉด์ ์œผ๋กœ ๋‚˜๋ˆ„๋Š” ์ธต(๊ตฌ์—ญ) ๋งŒ๋“ค๊ธฐ (๋น ๋ฅธ ๊ทผ์‚ฌ ๋ฒ„์ „)\n",
707
+ "# - ํด๋ฆฌ๊ณค ์•ˆ์— ๋ฌด์ž‘์œ„ ์  ๋งŽ์ด ๋ฝ‘๊ณ \n",
708
+ "# - x์ขŒํ‘œ ๊ธฐ์ค€์œผ๋กœ 6๋“ฑ๋ถ„(quantile) โ†’ ๊ฑฐ์˜ ๋™์ผ๋ฉด์  ์ŠคํŠธ๋ฆฝ\n",
709
+ "# ===========================\n",
710
+ "def make_equal_area_strata(poly, n_strata=6, n_mc=10000):\n",
711
+ " \"\"\"\n",
712
+ " poly๋ฅผ x์ถ• ๋ฐฉํ–ฅ ์ŠคํŠธ๋ฆฝ n_strata๊ฐœ๋กœ ๋‚˜๋ˆ„๋˜,\n",
713
+ " ํด๋ฆฌ๊ณค ๋‚ด๋ถ€์— ๊ท ์ผ ์ƒ˜ํ”Œ๋งํ•œ ์ ๋“ค์˜ x์ขŒํ‘œ ๋ถ„ํฌ๋ฅผ ์ด์šฉํ•ด\n",
714
+ " '๊ทผ์‚ฌ ๋™์ผ๋ฉด์ '์ด ๋˜๋„๋ก ์ž๋ฅธ๋‹ค.\n",
715
+ " - n_mc: ๋ชฌํ…Œ์นด๋ฅผ๋กœ ํฌ์ธํŠธ ๊ฐœ์ˆ˜ (๋งŽ์„์ˆ˜๋ก ๋ฉด์  ๊ทผ์‚ฌ ์ •ํ™•, ๋А๋ ค์ง)\n",
716
+ " \"\"\"\n",
717
+ "\n",
718
+ " # 1) ํด๋ฆฌ๊ณค ๋‚ด๋ถ€์— ๋ชฌํ…Œ์นด๋ฅผ๋กœ ํฌ์ธํŠธ ์ƒ์„ฑ\n",
719
+ " mc_points = random_points_in_polygon(poly, n_mc)\n",
720
+ " xs = np.array([p.x for p in mc_points])\n",
721
+ "\n",
722
+ " # 2) x์ขŒํ‘œ ์ •๋ ฌ ํ›„, n_strata๋“ฑ๋ถ„ quantile ์œ„์น˜ ์ฐพ๊ธฐ\n",
723
+ " xs_sorted = np.sort(xs)\n",
724
+ " cuts = []\n",
725
+ " for i in range(1, n_strata):\n",
726
+ " q = i / n_strata\n",
727
+ " idx = int(q * len(xs_sorted))\n",
728
+ " cuts.append(xs_sorted[idx])\n",
729
+ "\n",
730
+ " # 3) ์ž˜๋ผ๋‚ผ x ๊ฒฝ๊ณ„ ๋งŒ๋“ค๊ธฐ (์ขŒ/์šฐ ๋ ํฌํ•จ)\n",
731
+ " minx, miny, maxx, maxy = poly.bounds\n",
732
+ " x_edges = [minx] + cuts + [maxx]\n",
733
+ "\n",
734
+ " # 4) ๊ฐ ์ŠคํŠธ๋ฆฝ๊ณผ ํด๋ฆฌ๊ณค์˜ ๊ต์ฐจ๋กœ strata ์ƒ์„ฑ (intersection์€ ์ŠคํŠธ๋ฆฝ ๊ฐœ์ˆ˜๋งŒํผ ๋”ฑ 6๋ฒˆ)\n",
735
+ " strata_geoms = []\n",
736
+ " for j in range(n_strata):\n",
737
+ " cell = box(x_edges[j], miny, x_edges[j + 1], maxy)\n",
738
+ " inter = poly.intersection(cell)\n",
739
+ " if not inter.is_empty and inter.area > 0:\n",
740
+ " strata_geoms.append(inter)\n",
741
+ "\n",
742
+ " strata_gdf = gpd.GeoDataFrame(\n",
743
+ " {\"stratum_id\": range(len(strata_geoms))},\n",
744
+ " geometry=strata_geoms,\n",
745
+ " crs=\"EPSG:5179\"\n",
746
+ " )\n",
747
+ " return strata_gdf\n",
748
+ "\n",
749
+ "\n",
750
+ "\n",
751
+ "\n",
752
+ "# ===========================\n",
753
+ "# 4-1. ์ธตํ™” ์ƒ˜ํ”Œ๋ง (๋ฉด์  ๋น„๋ก€ ํ• ๋‹น, ๊ธฐ์กด)\n",
754
+ "# ===========================\n",
755
+ "def stratified_sampling(poly_strata_gdf, n_total):\n",
756
+ " areas = poly_strata_gdf.geometry.area.values\n",
757
+ " props = areas / areas.sum()\n",
758
+ "\n",
759
+ " # ๋ฉด์  ๋น„๋ก€ ์ดˆ๊ธฐ ํ• ๋‹น\n",
760
+ " n_alloc = np.round(props * n_total).astype(int)\n",
761
+ "\n",
762
+ " # ์ดํ•ฉ ๋ณด์ • (sum == n_total ๋˜๋„๋ก)\n",
763
+ " diff = n_total - n_alloc.sum()\n",
764
+ " if diff > 0:\n",
765
+ " # ๋” ํ•„์š”ํ•œ ๋งŒํผ, ๋ฉด์ ์ด ํฐ ์ธต๋ถ€ํ„ฐ 1์”ฉ ์ถ”๊ฐ€\n",
766
+ " idx_order = np.argsort(-areas)\n",
767
+ " for k in range(diff):\n",
768
+ " n_alloc[idx_order[k % len(n_alloc)]] += 1\n",
769
+ " elif diff < 0:\n",
770
+ " # ๋„ˆ๋ฌด ๋งŽ์ด ํ• ๋‹น๋œ ๊ฒฝ์šฐ, ๋ฉด์ ์ด ์ž‘์€ ์ธต๋ถ€ํ„ฐ 1์”ฉ ๊ฐ์†Œ (0 ๋ฐ‘์œผ๋กœ๋Š” ์•ˆ ๊ฐ€๊ฒŒ)\n",
771
+ " idx_order = np.argsort(areas)\n",
772
+ " k = 0\n",
773
+ " while diff < 0:\n",
774
+ " i = idx_order[k % len(n_alloc)]\n",
775
+ " if n_alloc[i] > 1: # ์ตœ์†Œ 1๊ฐœ๋Š” ์œ ์ง€\n",
776
+ " n_alloc[i] -= 1\n",
777
+ " diff += 1\n",
778
+ " k += 1\n",
779
+ "\n",
780
+ " # ๊ฐ ์ธต์—์„œ ์ƒ˜ํ”Œ๋ง\n",
781
+ " all_points = []\n",
782
+ " stratum_ids = []\n",
783
+ " for geom, sid, n_i in zip(poly_strata_gdf.geometry,\n",
784
+ " poly_strata_gdf[\"stratum_id\"],\n",
785
+ " n_alloc):\n",
786
+ " pts = random_points_in_polygon(geom, n_i)\n",
787
+ " all_points.extend(pts)\n",
788
+ " stratum_ids.extend([sid] * len(pts))\n",
789
+ "\n",
790
+ " sample_gdf = gpd.GeoDataFrame(\n",
791
+ " {\"stratum_id\": stratum_ids},\n",
792
+ " geometry=all_points,\n",
793
+ " crs=poly_strata_gdf.crs,\n",
794
+ " )\n",
795
+ " return sample_gdf, n_alloc\n",
796
+ "\n",
797
+ "\n",
798
+ "# ===========================\n",
799
+ "# 4-2. ์ธตํ™” ์ƒ˜ํ”Œ๋ง (๋™์ผ ํ‘œ๋ณธ์ˆ˜ ํ• ๋‹น: n_total์„ ์ธต ๊ฐœ์ˆ˜๋กœ ๊ท ๋“ฑ ๋ถ„๋ฐฐ)\n",
800
+ "# ===========================\n",
801
+ "def stratified_sampling_equal_n(poly_strata_gdf, n_total):\n",
802
+ " n_strata = len(poly_strata_gdf)\n",
803
+ " base = n_total // n_strata\n",
804
+ " rem = n_total % n_strata\n",
805
+ "\n",
806
+ " n_alloc = np.array([base] * n_strata)\n",
807
+ " for i in range(rem):\n",
808
+ " n_alloc[i] += 1\n",
809
+ "\n",
810
+ " all_points = []\n",
811
+ " stratum_ids = []\n",
812
+ " for geom, sid, n_i in zip(poly_strata_gdf.geometry,\n",
813
+ " poly_strata_gdf[\"stratum_id\"],\n",
814
+ " n_alloc):\n",
815
+ " pts = random_points_in_polygon(geom, n_i)\n",
816
+ " all_points.extend(pts)\n",
817
+ " stratum_ids.extend([sid] * len(pts))\n",
818
+ "\n",
819
+ " sample_gdf = gpd.GeoDataFrame(\n",
820
+ " {\"stratum_id\": stratum_ids},\n",
821
+ " geometry=all_points,\n",
822
+ " crs=poly_strata_gdf.crs,\n",
823
+ " )\n",
824
+ " return sample_gdf, n_alloc\n",
825
+ "\n",
826
+ "\n",
827
+ "# ===========================\n",
828
+ "# 5. ํด๋ฆฌํ•‘์šฉ ํŒจ์น˜ ์ƒ์„ฑ\n",
829
+ "# ===========================\n",
830
+ "def make_clip_patch_and_poly_web(boundary_gdf_5179):\n",
831
+ " \"\"\"\n",
832
+ " ๊ฒฝ๊ณ„ gdf(EPGS:5179)์—์„œ Web Mercator(3857) ๊ธฐ์ค€ ํด๋ฆฌ๊ณค + clip์šฉ PathPatch ๋ฐ˜ํ™˜\n",
833
+ " \"\"\"\n",
834
+ " gdf_web = boundary_gdf_5179.to_crs(epsg=3857)\n",
835
+ " poly_web = gdf_web.geometry.unary_union\n",
836
+ "\n",
837
+ " # MultiPolygon์ด๋ฉด ๊ฐ€์žฅ ํฐ ๊ฒƒ ์‚ฌ์šฉ\n",
838
+ " if poly_web.geom_type == \"MultiPolygon\":\n",
839
+ " poly_web = max(poly_web.geoms, key=lambda g: g.area)\n",
840
+ "\n",
841
+ " x, y = poly_web.exterior.coords.xy\n",
842
+ " coords = np.vstack([x, y]).T\n",
843
+ "\n",
844
+ " Path = mpath.Path\n",
845
+ " path_data = [(Path.MOVETO, coords[0])]\n",
846
+ " for c in coords[1:]:\n",
847
+ " path_data.append((Path.LINETO, c))\n",
848
+ " path_data.append((Path.CLOSEPOLY, coords[0]))\n",
849
+ "\n",
850
+ " codes, verts = zip(*path_data)\n",
851
+ " clip_path = mpath.Path(verts, codes)\n",
852
+ " clip_patch = mpatches.PathPatch(\n",
853
+ " clip_path,\n",
854
+ " facecolor=\"none\",\n",
855
+ " edgecolor=\"none\"\n",
856
+ " )\n",
857
+ "\n",
858
+ " return clip_patch, poly_web, gdf_web\n",
859
+ "\n",
860
+ "\n",
861
+ "# ===========================\n",
862
+ "# 6. ์‹œ๊ฐํ™” 1: ์›๋ณธ ๊ฒฝ๊ณ„๋งŒ (์ƒ˜ํ”Œ ์—†์Œ)\n",
863
+ "# ===========================\n",
864
+ "def plot_base(gdf_boundary_5179,\n",
865
+ " out_png=\"inha_boundary_base.png\"):\n",
866
+ "\n",
867
+ " clip_patch, poly_web, boundary_web = make_clip_patch_and_poly_web(gdf_boundary_5179)\n",
868
+ "\n",
869
+ " fig, ax = plt.subplots(figsize=(12, 10))\n",
870
+ "\n",
871
+ " # clip ํŒจ์น˜\n",
872
+ " ax.add_patch(clip_patch)\n",
873
+ "\n",
874
+ " # ๊ฒฝ๊ณ„(์„ )\n",
875
+ " boundary_web.boundary.plot(ax=ax, linewidth=2, edgecolor=\"black\")\n",
876
+ "\n",
877
+ " # basemap\n",
878
+ " ctx.add_basemap(ax, source=ctx.providers.OpenStreetMap.Mapnik, alpha=0.9)\n",
879
+ "\n",
880
+ " # basemap ํด๋ฆฌํ•‘\n",
881
+ " if ax.images:\n",
882
+ " ax.images[-1].set_clip_path(clip_patch)\n",
883
+ "\n",
884
+ " # ์ถ• ๋ฒ”์œ„\n",
885
+ " minx, miny, maxx, maxy = poly_web.bounds\n",
886
+ " ax.set_xlim(minx, maxx)\n",
887
+ " ax.set_ylim(miny, maxy)\n",
888
+ "\n",
889
+ " # ๋ฐ”๊นฅ ํˆฌ๋ช…\n",
890
+ " ax.set_facecolor(\"none\")\n",
891
+ " fig.patch.set_alpha(0.0)\n",
892
+ "\n",
893
+ " ax.axis(\"off\")\n",
894
+ " plt.tight_layout()\n",
895
+ " plt.savefig(out_png, dpi=300, bbox_inches=\"tight\", transparent=True)\n",
896
+ " plt.close(fig)\n",
897
+ " print(f\"โœ“ ์›๋ณธ ๊ฒฝ๊ณ„ ์ง€๋„ ์ €์žฅ: {out_png}\")\n",
898
+ "\n",
899
+ "\n",
900
+ "# ===========================\n",
901
+ "# 7. ์‹œ๊ฐํ™” 2: SRS ์ƒ˜ํ”Œ\n",
902
+ "# ===========================\n",
903
+ "def plot_srs(gdf_boundary_5179, srs_points_5179,\n",
904
+ " out_png=\"inha_sampling_srs.png\"):\n",
905
+ "\n",
906
+ " clip_patch, poly_web, boundary_web = make_clip_patch_and_poly_web(gdf_boundary_5179)\n",
907
+ " pts_web = srs_points_5179.to_crs(epsg=3857)\n",
908
+ "\n",
909
+ " fig, ax = plt.subplots(figsize=(12, 10))\n",
910
+ "\n",
911
+ " ax.add_patch(clip_patch)\n",
912
+ "\n",
913
+ " # ๊ฒฝ๊ณ„(์„ )\n",
914
+ " boundary_web.boundary.plot(ax=ax, linewidth=2, edgecolor=\"black\")\n",
915
+ "\n",
916
+ " # SRS ์ \n",
917
+ " pts_web.plot(ax=ax, markersize=30, color=\"red\", alpha=0.9)\n",
918
+ "\n",
919
+ " # basemap\n",
920
+ " ctx.add_basemap(ax, source=ctx.providers.OpenStreetMap.Mapnik, alpha=0.9)\n",
921
+ "\n",
922
+ " # basemap ํด๋ฆฌํ•‘\n",
923
+ " if ax.images:\n",
924
+ " ax.images[-1].set_clip_path(clip_patch)\n",
925
+ "\n",
926
+ " # ์ถ• ๋ฒ”์œ„\n",
927
+ " minx, miny, maxx, maxy = poly_web.bounds\n",
928
+ " ax.set_xlim(minx, maxx)\n",
929
+ " ax.set_ylim(miny, maxy)\n",
930
+ "\n",
931
+ " # ๋ฐ”๊นฅ ํˆฌ๋ช…\n",
932
+ " ax.set_facecolor(\"none\")\n",
933
+ " fig.patch.set_alpha(0.0)\n",
934
+ "\n",
935
+ " ax.axis(\"off\")\n",
936
+ " plt.tight_layout()\n",
937
+ " plt.savefig(out_png, dpi=300, bbox_inches=\"tight\", transparent=True)\n",
938
+ " plt.close(fig)\n",
939
+ " print(f\"โœ“ SRS ์ƒ˜ํ”Œ ์ง€๋„ ์ €์žฅ: {out_png}\")\n",
940
+ "\n",
941
+ "\n",
942
+ "# ===========================\n",
943
+ "# 8-1. ์‹œ๊ฐํ™” 3: ์ธตํ™” ์ƒ˜ํ”Œ (๊ฒฉ์ž + ๋ฉด์ ๏ฟฝ๏ฟฝ๋ก€)\n",
944
+ "# ===========================\n",
945
+ "def plot_strata(gdf_boundary_5179, strata_gdf_5179, sample_gdf_5179,\n",
946
+ " out_png=\"inha_sampling_strata.png\"):\n",
947
+ "\n",
948
+ " clip_patch, poly_web, boundary_web = make_clip_patch_and_poly_web(gdf_boundary_5179)\n",
949
+ " strata_web = strata_gdf_5179.to_crs(epsg=3857)\n",
950
+ " sample_web = sample_gdf_5179.to_crs(epsg=3857)\n",
951
+ "\n",
952
+ " fig, ax = plt.subplots(figsize=(12, 10))\n",
953
+ "\n",
954
+ " ax.add_patch(clip_patch)\n",
955
+ "\n",
956
+ " # ์ธต(๊ตฌ์—ญ) ์˜์—ญ ์ƒ‰์น \n",
957
+ " strata_web.plot(\n",
958
+ " ax=ax,\n",
959
+ " column=\"stratum_id\",\n",
960
+ " alpha=0.35,\n",
961
+ " edgecolor=\"black\",\n",
962
+ " linewidth=1,\n",
963
+ " legend=False\n",
964
+ " )\n",
965
+ "\n",
966
+ " # ์บ ํผ์Šค ์ „์ฒด ๊ฒฝ๊ณ„\n",
967
+ " boundary_web.boundary.plot(ax=ax, linewidth=2, edgecolor=\"black\")\n",
968
+ "\n",
969
+ " # ์ธตํ™” ์ƒ˜ํ”Œ ์  (์ธต๋ณ„ ์ƒ‰ ๊ตฌ๋ถ„)\n",
970
+ " sample_web.plot(\n",
971
+ " ax=ax,\n",
972
+ " column=\"stratum_id\",\n",
973
+ " markersize=30,\n",
974
+ " cmap=\"tab10\",\n",
975
+ " legend=False\n",
976
+ " )\n",
977
+ "\n",
978
+ " # basemap\n",
979
+ " ctx.add_basemap(ax, source=ctx.providers.OpenStreetMap.Mapnik, alpha=0.9)\n",
980
+ "\n",
981
+ " # basemap ํด๋ฆฌํ•‘\n",
982
+ " if ax.images:\n",
983
+ " ax.images[-1].set_clip_path(clip_patch)\n",
984
+ "\n",
985
+ " # ์ถ• ๋ฒ”์œ„\n",
986
+ " minx, miny, maxx, maxy = poly_web.bounds\n",
987
+ " ax.set_xlim(minx, maxx)\n",
988
+ " ax.set_ylim(miny, maxy)\n",
989
+ "\n",
990
+ " # ๋ฐ”๊นฅ ํˆฌ๋ช…\n",
991
+ " ax.set_facecolor(\"none\")\n",
992
+ " fig.patch.set_alpha(0.0)\n",
993
+ "\n",
994
+ " ax.axis(\"off\")\n",
995
+ " plt.tight_layout()\n",
996
+ " plt.savefig(out_png, dpi=300, bbox_inches=\"tight\", transparent=True)\n",
997
+ " plt.close(fig)\n",
998
+ " print(f\"โœ“ ์ธตํ™” ์ƒ˜ํ”Œ ์ง€๋„ ์ €์žฅ: {out_png}\")\n",
999
+ "\n",
1000
+ "\n",
1001
+ "# ===========================\n",
1002
+ "# 8-2. ์‹œ๊ฐํ™” 4: ์ธตํ™” ์ƒ˜ํ”Œ (๋™์ผ๋ฉด์  strata + ๋™์ผ ํ‘œ๋ณธ์ˆ˜)\n",
1003
+ "# ===========================\n",
1004
+ "def plot_strata_equal(gdf_boundary_5179, strata_gdf_5179, sample_gdf_5179,\n",
1005
+ " out_png=\"inha_sampling_strata_equalarea.png\"):\n",
1006
+ "\n",
1007
+ " clip_patch, poly_web, boundary_web = make_clip_patch_and_poly_web(gdf_boundary_5179)\n",
1008
+ " strata_web = strata_gdf_5179.to_crs(epsg=3857)\n",
1009
+ " sample_web = sample_gdf_5179.to_crs(epsg=3857)\n",
1010
+ "\n",
1011
+ " fig, ax = plt.subplots(figsize=(12, 10))\n",
1012
+ "\n",
1013
+ " ax.add_patch(clip_patch)\n",
1014
+ "\n",
1015
+ " # ์ธต(๊ตฌ์—ญ) ์˜์—ญ ์ƒ‰์น \n",
1016
+ " strata_web.plot(\n",
1017
+ " ax=ax,\n",
1018
+ " column=\"stratum_id\",\n",
1019
+ " alpha=0.35,\n",
1020
+ " edgecolor=\"black\",\n",
1021
+ " linewidth=1,\n",
1022
+ " legend=False\n",
1023
+ " )\n",
1024
+ "\n",
1025
+ " # ์บ ํผ์Šค ์ „์ฒด ๊ฒฝ๊ณ„\n",
1026
+ " boundary_web.boundary.plot(ax=ax, linewidth=2, edgecolor=\"black\")\n",
1027
+ "\n",
1028
+ " # ์ธตํ™” ์ƒ˜ํ”Œ ์  (์ธต๋ณ„ ์ƒ‰ ๊ตฌ๋ถ„)\n",
1029
+ " sample_web.plot(\n",
1030
+ " ax=ax,\n",
1031
+ " column=\"stratum_id\",\n",
1032
+ " markersize=30,\n",
1033
+ " cmap=\"tab10\",\n",
1034
+ " legend=False\n",
1035
+ " )\n",
1036
+ "\n",
1037
+ " # basemap\n",
1038
+ " ctx.add_basemap(ax, source=ctx.providers.OpenStreetMap.Mapnik, alpha=0.9)\n",
1039
+ "\n",
1040
+ " # basemap ํด๋ฆฌํ•‘\n",
1041
+ " if ax.images:\n",
1042
+ " ax.images[-1].set_clip_path(clip_patch)\n",
1043
+ "\n",
1044
+ " # ์ถ• ๋ฒ”์œ„\n",
1045
+ " minx, miny, maxx, maxy = poly_web.bounds\n",
1046
+ " ax.set_xlim(minx, maxx)\n",
1047
+ " ax.set_ylim(miny, maxy)\n",
1048
+ "\n",
1049
+ " # ๋ฐ”๊นฅ ํˆฌ๋ช…\n",
1050
+ " ax.set_facecolor(\"none\")\n",
1051
+ " fig.patch.set_alpha(0.0)\n",
1052
+ "\n",
1053
+ "\n",
1054
+ " ax.axis(\"off\")\n",
1055
+ " plt.tight_layout()\n",
1056
+ " plt.savefig(out_png, dpi=300, bbox_inches=\"tight\", transparent=True)\n",
1057
+ " plt.close(fig)\n",
1058
+ " print(f\"โœ“ ๋™์ผ๋ฉด์  ์ธตํ™” ์ƒ˜ํ”Œ ์ง€๋„ ์ €์žฅ: {out_png}\")\n",
1059
+ "\n",
1060
+ "\n",
1061
+ "# ===========================\n",
1062
+ "# 9. ๋ฉ”์ธ ์‹คํ–‰\n",
1063
+ "# ===========================\n",
1064
+ "if __name__ == \"__main__\":\n",
1065
+ " # 1) ๊ฒฝ๊ณ„ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ\n",
1066
+ " boundary_gdf, boundary_poly = load_inha_boundary(\"inha_boundary.shp\")\n",
1067
+ "\n",
1068
+ " # 2) SRS ์ƒ˜ํ”Œ๋ง (์˜ˆ: 40๊ฐœ)\n",
1069
+ " n_srs = 60\n",
1070
+ " srs_points = random_points_in_polygon(boundary_poly, n_srs)\n",
1071
+ " srs_gdf = gpd.GeoDataFrame(\n",
1072
+ " {\"design\": [\"SRS\"] * len(srs_points)},\n",
1073
+ " geometry=srs_points,\n",
1074
+ " crs=\"EPSG:5179\"\n",
1075
+ " )\n",
1076
+ "\n",
1077
+ " # 3-1) ๊ฒฉ์ž ์ธต(๊ตฌ์—ญ) ๋งŒ๋“ค๊ณ  ์ธตํ™” ์ƒ˜ํ”Œ๋ง (๋ฉด์ ๋น„๋ก€)\n",
1078
+ " strata_gdf = make_strata(boundary_poly, n_rows=2, n_cols=3)\n",
1079
+ " strat_sample_gdf, n_alloc = stratified_sampling(strata_gdf, n_total=60)\n",
1080
+ " print(\"๊ฒฉ์ž ์ธต๋ณ„ ํ‘œ๋ณธ ์ˆ˜ ํ• ๋‹น (๋ฉด์ ๋น„๋ก€):\", n_alloc)\n",
1081
+ "\n",
1082
+ " # 3-2) ๋™์ผ๋ฉด์  strata ๋งŒ๋“ค๊ณ  ์ธตํ™” ์ƒ˜ํ”Œ๋ง (๋™์ผ ํ‘œ๋ณธ์ˆ˜)\n",
1083
+ " equal_strata_gdf = make_equal_area_strata(boundary_poly, n_strata=6)\n",
1084
+ " equal_strat_sample_gdf, n_alloc_equal = stratified_sampling_equal_n(equal_strata_gdf, n_total=60)\n",
1085
+ " print(\"๋™์ผ๋ฉด์  ์ธต๋ณ„ ํ‘œ๋ณธ ์ˆ˜ ํ• ๋‹น (๋™์ผ ํ‘œ๋ณธ์ˆ˜):\", n_alloc_equal)\n",
1086
+ "\n",
1087
+ " # 4) ์›๋ณธ ๊ฒฝ๊ณ„ ์ง€๋„ (๋ฐ”๊นฅ ํˆฌ๋ช…)\n",
1088
+ " plot_base(boundary_gdf, out_png=\"inha_boundary_base.png\")\n",
1089
+ "\n",
1090
+ " # 5) SRS ์ง€๋„\n",
1091
+ " plot_srs(boundary_gdf, srs_gdf,\n",
1092
+ " out_png=\"inha_sampling_srs.png\")\n",
1093
+ "\n",
1094
+ " # 6) ์ธตํ™” ์ง€๋„ (๊ฒฉ์ž + ๋ฉด์ ๋น„๋ก€)\n",
1095
+ " plot_strata(boundary_gdf, strata_gdf, strat_sample_gdf,\n",
1096
+ " out_png=\"inha_sampling_strata.png\")\n",
1097
+ "\n",
1098
+ " # 7) ์ธตํ™” ์ง€๋„ (๋™์ผ๋ฉด์  strata + ๋™์ผ ํ‘œ๋ณธ์ˆ˜)\n",
1099
+ " plot_strata_equal(boundary_gdf, equal_strata_gdf, equal_strat_sample_gdf,\n",
1100
+ " out_png=\"inha_sampling_strata_equalarea.png\")\n",
1101
+ "\n",
1102
+ " # 8) ์ขŒํ‘œ ํ™•์ธ์šฉ ์ถœ๋ ฅ\n",
1103
+ " print(\"\\n[SRS ์ƒ˜ํ”Œ ์ขŒํ‘œ (EPSG:5179)]\")\n",
1104
+ " print(srs_gdf.head())\n",
1105
+ "\n",
1106
+ " print(\"\\n[๊ฒฉ์ž ์ธตํ™” ์ƒ˜ํ”Œ ์ขŒํ‘œ (EPSG:5179)]\")\n",
1107
+ " print(strat_sample_gdf.head())\n",
1108
+ "\n",
1109
+ " print(\"\\n[๋™์ผ๋ฉด์  ์ธตํ™” ์ƒ˜ํ”Œ ์ขŒํ‘œ (EPSG:5179)]\")\n",
1110
+ " print(equal_strat_sample_gdf.head())\n"
1111
+ ]
1112
+ }
1113
+ ],
1114
+ "metadata": {
1115
+ "kernelspec": {
1116
+ "display_name": "torch_ev",
1117
+ "language": "python",
1118
+ "name": "python3"
1119
+ },
1120
+ "language_info": {
1121
+ "codemirror_mode": {
1122
+ "name": "ipython",
1123
+ "version": 3
1124
+ },
1125
+ "file_extension": ".py",
1126
+ "mimetype": "text/x-python",
1127
+ "name": "python",
1128
+ "nbconvert_exporter": "python",
1129
+ "pygments_lexer": "ipython3",
1130
+ "version": "3.10.16"
1131
+ }
1132
+ },
1133
+ "nbformat": 4,
1134
+ "nbformat_minor": 5
1135
+ }
๋ฐ์ดํ„ฐ ์ˆ˜์ง‘/inha_boundary_with_points.png ADDED

Git LFS Details

  • SHA256: b7ca5728553805cd83504b7e19ae7e81bdc073f5eb2afa1bb926921622dfdab7
  • Pointer size: 132 Bytes
  • Size of remote file: 3.43 MB
๋ฐ์ดํ„ฐ ์ˆ˜์ง‘/๊น€๋‹ค์›.csv ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ lat,lon,dB
2
+ 37.4516638,126.6527864,50.0
3
+ 37.4512905,126.653364,49.0
4
+ 37.4514279,126.652793,48.0
5
+ 37.4511085,126.6535225,49.0
6
+ 37.4510701,126.6531148,48.0
7
+ 37.4513171,126.652632,47.0
8
+ 37.4510595,126.6526267,50.0
9
+ 37.4510309,126.6528753,51.0
10
+ 37.4510631,126.6536844,57.0
11
+ 37.450863,126.6535503,52.0
12
+ 37.4507669,126.6537015,51.0
13
+ 37.4512012,126.6539697,47.0
14
+ 37.4505606,126.6533733,47.0
15
+ 37.4509074,126.6521297,58.0
16
+ 37.4507437,126.6524077,49.0
17
+ 37.4506799,126.6527081,48.0
18
+ 37.4505351,126.6524077,53.0
19
+ 37.4503988,126.6532875,47.0
20
+ 37.4503967,126.6529463,46.0
21
+ 37.4512256,126.6514948,47.0
22
+ 37.4510425,126.6515538,46.0
23
+ 37.451127,126.6513348,46.0
24
+ 37.4513746,126.6517523,42.0
25
+ 37.4506117,126.6531748,44.0
26
+ 37.4509056,126.6523755,42.0
27
+ 37.4517914,126.6530514,55.0
28
+ 37.4514975,126.6537273,42.0
29
+ 37.4515699,126.6534377,41.0
30
+ 37.4517164,126.6525593,39.0
31
+ 37.4514336,126.6522414,50.0
32
+ 37.4512906,126.6521779,42.0
33
+ 37.4506252,126.6513859,53.0
34
+ 37.4504676,126.6517346,45.0
35
+ 37.4502887,126.6514664,50.0
36
+ 37.4501865,126.6517293,47.0
37
+ 37.4500971,126.651917,49.0
38
+ 37.4499438,126.6512143,50.0
39
+ 37.4499054,126.6506027,51.0
40
+ 37.4503398,126.6505276,49.0
41
+ 37.4505698,126.6506188,43.0
42
+ 37.4506251,126.6509514,45.0
43
+ 37.4500532,126.6527818,46.0
44
+ 37.4499255,126.6529267,43.0
45
+ 37.4497977,126.6530554,44.0
46
+ 37.4495805,126.6526316,48.0
47
+ 37.449474,126.6528355,52.0
48
+ 37.4492398,126.6524492,53.0
49
+ 37.4489332,126.6520201,50.0
50
+ 37.4489204,126.6522293,46.0
51
+ 37.4479436,126.6529785,49.0
52
+ 37.4486122,126.6533164,46.0
53
+ 37.4486569,126.6536919,43.0
54
+ 37.4489806,126.6539548,44.0
55
+ 37.4488827,126.6542498,47.0
56
+ 37.4495172,126.6545878,38.0
57
+ 37.4494917,126.6541855,51.0
58
+ 37.4496024,126.654502,52.0
59
+ 37.4499814,126.6539816,52.0
60
+ 37.4497003,126.6544966,46.0
61
+ 37.4495939,126.654797,54.0
62
+ 37.4493171,126.6552154,46.0
63
+ 37.4491893,126.6552101,47.0
64
+ 37.4491978,126.655076,45.0
65
+ 37.4490317,126.6551242,47.0
66
+ 37.4513577,126.6545939,51.0
67
+ 37.4505187,126.6540735,53.0
68
+ 37.4511234,126.6544705,60.0
69
+ 37.4508636,126.6558974,43.0
70
+ 37.4509659,126.6549157,44.0
71
+ 37.4508935,126.6552698,39.0
72
+ 37.4505826,126.6550338,56.0
73
+ 37.4506465,126.6546851,52.0
74
+ 37.4503569,126.6549479,59.0
75
+ 37.4504548,126.6550123,54.0
76
+ 37.4505655,126.655243,42.0
77
+ 37.4503654,126.655495,47.0
78
+ 37.4505016,126.6553824,41.0
79
+ 37.4500523,126.6554818,41.0
80
+ 37.4498416,126.6553664,46.0
81
+ 37.450259,126.6542881,44.0
82
+ 37.4502206,126.6544383,43.0
83
+ 37.4500119,126.6549318,44.0
84
+ 37.449931,126.6551249,51.0
85
+ 37.4499437,126.6552644,46.0
86
+ 37.4501013,126.6545617,50.0
87
+ 37.4501525,126.654213,48.0
88
+ 37.4499771,126.65462,48.0
89
+ 37.4497514,126.6551403,52.0
90
+ 37.4498331,126.6546207,48.0
91
+ 37.4497777,126.6547173,52.0
92
+ 37.4490231,126.6561154,0.0
๋ฐ์ดํ„ฐ ์ˆ˜์ง‘/๊น€๋‹ค์›.ipynb ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 26,
6
+ "id": "be7e70ab",
7
+ "metadata": {},
8
+ "outputs": [
9
+ {
10
+ "name": "stderr",
11
+ "output_type": "stream",
12
+ "text": [
13
+ "C:\\Users\\USER\\AppData\\Local\\Temp\\ipykernel_20792\\1924603428.py:19: DeprecationWarning: The 'unary_union' attribute is deprecated, use the 'union_all()' method instead.\n",
14
+ " poly = gdf_web.geometry.unary_union\n"
15
+ ]
16
+ },
17
+ {
18
+ "name": "stdout",
19
+ "output_type": "stream",
20
+ "text": [
21
+ "ํฌ์ธํŠธ ๊ฐœ์ˆ˜: 90\n",
22
+ "์ €์žฅ ์™„๋ฃŒ: inha_boundary_with_points.png\n"
23
+ ]
24
+ }
25
+ ],
26
+ "source": [
27
+ "import geopandas as gpd\n",
28
+ "import pandas as pd\n",
29
+ "import matplotlib.pyplot as plt\n",
30
+ "import contextily as ctx\n",
31
+ "import numpy as np\n",
32
+ "import matplotlib.path as mpath\n",
33
+ "import matplotlib.patches as mpatches\n",
34
+ "from shapely import wkt\n",
35
+ "\n",
36
+ "def plot_clipped_map_with_points(\n",
37
+ " shp_path=r\"C:\\Users\\USER\\iCloudDrive\\์ธํ•˜๋Œ€ํ•™๊ต\\5ํ•™๋…„ 1ํ•™๊ธฐ\\ํ‘œ๋ณธ๋ก  ๋ฐ ์‹ค์Šต\\TeamProject\\Data\\inha_boundary.shp\",\n",
38
+ " points_csv=r\"C:\\Users\\USER\\iCloudDrive\\์ธํ•˜๋Œ€ํ•™๊ต\\5ํ•™๋…„ 1ํ•™๊ธฐ\\ํ‘œ๋ณธ๋ก  ๋ฐ ์‹ค์Šต\\TeamProject\\๋ฐ์ดํ„ฐ ์ˆ˜์ง‘\\๊น€๋‹ค์›.csv\",\n",
39
+ " out_png=\"inha_boundary_with_points.png\",\n",
40
+ "):\n",
41
+ " # ---------- 1) ์บ ํผ์Šค ๊ฒฝ๊ณ„ ----------\n",
42
+ " gdf = gpd.read_file(shp_path)\n",
43
+ " gdf_web = gdf.to_crs(epsg=3857)\n",
44
+ "\n",
45
+ " poly = gdf_web.geometry.unary_union\n",
46
+ " if poly.geom_type == \"MultiPolygon\":\n",
47
+ " poly = max(poly.geoms, key=lambda g: g.area)\n",
48
+ "\n",
49
+ " x, y = poly.exterior.coords.xy\n",
50
+ " coords = np.vstack([x, y]).T\n",
51
+ "\n",
52
+ " Path = mpath.Path\n",
53
+ " path_data = [(Path.MOVETO, coords[0])]\n",
54
+ " for c in coords[1:]:\n",
55
+ " path_data.append((Path.LINETO, c))\n",
56
+ " path_data.append((Path.CLOSEPOLY, coords[0]))\n",
57
+ "\n",
58
+ " codes, verts = zip(*path_data)\n",
59
+ " clip_path = mpath.Path(verts, codes)\n",
60
+ " clip_patch = mpatches.PathPatch(clip_path, facecolor=\"none\", edgecolor=\"none\")\n",
61
+ "\n",
62
+ " # ---------- 2) CSV ํฌ์ธํŠธ ์ฝ๊ธฐ ----------\n",
63
+ " df_pts = pd.read_csv(points_csv, encoding=\"cp949\")\n",
64
+ "\n",
65
+ " # ์ปฌ๋Ÿผ ์ด๋ฆ„์— BOM ๋ถ™์–ด ์žˆ์„ ๊ฐ€๋Šฅ์„ฑ ์ฒ˜๋ฆฌ\n",
66
+ " df_pts.columns = [c.strip() for c in df_pts.columns]\n",
67
+ "\n",
68
+ " # WKT ์ปฌ๋Ÿผ ์ฐพ๊ธฐ\n",
69
+ " if \"WKT\" not in df_pts.columns:\n",
70
+ " # ์ด๋ฆ„์— 'WKT'๊ฐ€ ํฌํ•จ๋œ ์ปฌ๋Ÿผ ์ฐพ๊ธฐ\n",
71
+ " cand = [c for c in df_pts.columns if \"WKT\" in c.upper()]\n",
72
+ " if not cand:\n",
73
+ " # ๊ทธ๋ž˜๋„ ์—†์œผ๋ฉด ์ฒซ ๋ฒˆ์งธ ์ปฌ๋Ÿผ์„ WKT๋กœ ๊ฐ„์ฃผ\n",
74
+ " cand = [df_pts.columns[0]]\n",
75
+ " df_pts = df_pts.rename(columns={cand[0]: \"WKT\"})\n",
76
+ "\n",
77
+ " # dB ์ปฌ๋Ÿผ ์ฐพ๊ธฐ\n",
78
+ " if \"dB\" not in df_pts.columns:\n",
79
+ " value_cols = [c for c in df_pts.columns if c != \"WKT\"]\n",
80
+ " if not value_cols:\n",
81
+ " raise ValueError(\"dB ๊ฐ’์œผ๋กœ ์“ธ ์ˆซ์ž ์ปฌ๋Ÿผ์ด ์—†์Œ.\")\n",
82
+ " df_pts = df_pts.rename(columns={value_cols[0]: \"dB\"})\n",
83
+ "\n",
84
+ " # WKT โ†’ geometry\n",
85
+ " df_pts[\"geometry\"] = df_pts[\"WKT\"].apply(wkt.loads)\n",
86
+ " gdf_pts = gpd.GeoDataFrame(df_pts, geometry=\"geometry\", crs=4326)\n",
87
+ " gdf_pts_web = gdf_pts.to_crs(epsg=3857)\n",
88
+ "\n",
89
+ " print(\"ํฌ์ธํŠธ ๊ฐœ์ˆ˜:\", len(gdf_pts_web))\n",
90
+ "\n",
91
+ " # ---------- 3) ๊ทธ๋ฆผ ----------\n",
92
+ " fig, ax = plt.subplots(figsize=(15, 12))\n",
93
+ "\n",
94
+ " # ํด๋ฆฌ๊ณค: ํ…Œ๋‘๋ฆฌ๋งŒ\n",
95
+ " gdf_web.plot(\n",
96
+ " ax=ax,\n",
97
+ " facecolor=\"none\", # ๋‚ด๋ถ€ ์ƒ‰์น  X\n",
98
+ " edgecolor=\"darkblue\",\n",
99
+ " linewidth=2,\n",
100
+ " )\n",
101
+ "\n",
102
+ " ax.add_patch(clip_patch)\n",
103
+ "\n",
104
+ " # ๋ฒ ์ด์Šค๋งต\n",
105
+ " ctx.add_basemap(ax, source=ctx.providers.OpenStreetMap.Mapnik, alpha=0.7)\n",
106
+ " if ax.images:\n",
107
+ " ax.images[-1].set_clip_path(clip_patch)\n",
108
+ "\n",
109
+ " # ํฌ์ธํŠธ (์ปฌ๋Ÿฌ๋ฐ” ์—†์Œ)\n",
110
+ " gdf_pts_web.plot(\n",
111
+ " ax=ax,\n",
112
+ " column=\"dB\",\n",
113
+ " cmap=\"viridis\",\n",
114
+ " markersize=40,\n",
115
+ " edgecolor=\"black\",\n",
116
+ " linewidth=0.3,\n",
117
+ " alpha=0.9,\n",
118
+ " legend=False, # ์ปฌ๋Ÿฌ๋ฐ” ์ œ๊ฑฐ\n",
119
+ " zorder=5, # ํ•ญ์ƒ ์œ„์— ์˜ค๊ฒŒ\n",
120
+ " )\n",
121
+ "\n",
122
+ " minx, miny, maxx, maxy = poly.bounds\n",
123
+ " ax.set_xlim(minx, maxx)\n",
124
+ " ax.set_ylim(miny, maxy)\n",
125
+ " ax.axis(\"off\")\n",
126
+ "\n",
127
+ " plt.tight_layout()\n",
128
+ " plt.savefig(out_png, dpi=300, bbox_inches=\"tight\", facecolor=\"white\")\n",
129
+ " plt.close(fig)\n",
130
+ "\n",
131
+ " print(f\"์ €์žฅ ์™„๋ฃŒ: {out_png}\")\n",
132
+ "\n",
133
+ "\n",
134
+ "plot_clipped_map_with_points()\n"
135
+ ]
136
+ }
137
+ ],
138
+ "metadata": {
139
+ "kernelspec": {
140
+ "display_name": "torch_ev",
141
+ "language": "python",
142
+ "name": "python3"
143
+ },
144
+ "language_info": {
145
+ "codemirror_mode": {
146
+ "name": "ipython",
147
+ "version": 3
148
+ },
149
+ "file_extension": ".py",
150
+ "mimetype": "text/x-python",
151
+ "name": "python",
152
+ "nbconvert_exporter": "python",
153
+ "pygments_lexer": "ipython3",
154
+ "version": "3.10.16"
155
+ }
156
+ },
157
+ "nbformat": 4,
158
+ "nbformat_minor": 5
159
+ }
๋ฐ์ดํ„ฐ ์ˆ˜์ง‘/์ธก์ •๊ฒฐ๊ณผ.csv ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ dB
2
+ 45.0
3
+ 52.0
4
+ 55.0
5
+ 49.0
6
+ 56.0
7
+ 49.0
8
+ 37.0
9
+ 47.0
10
+ 41.0
11
+ 42.0
12
+ 59.0
13
+ 46.0
14
+ 43.0
15
+ 47.0
16
+ 53.0
17
+ 46.0
18
+ 50.0
19
+ 54.0
20
+ 44.0
21
+ 0.0
22
+ 47.0
23
+ 55.0
24
+ 47.0
25
+ 45.0
26
+ 53.0
27
+ 59.0
28
+ 58.0
29
+ 46.0
30
+ 63.0
31
+ 52.0
32
+ 51.0
33
+ 61.0
34
+ 38.0
35
+ 48.0
36
+ 54.0
37
+ 52.0
38
+ 46.0
39
+ 58.0
40
+ 52.0
41
+ 52.0
42
+ 44.0
43
+ 48.0
44
+ 46.0
45
+ 51.0
46
+ 43.0
47
+ 51.0
48
+ 56.0
49
+ 46.0
50
+ 50.0
51
+ 48.0
52
+ 52.0
53
+ 44.0
54
+ 41.0
55
+ 46.0
56
+ 49.0
57
+ 50.0
58
+ 48.0
59
+ 47.0
60
+ 43.0
61
+ 44.0
62
+ 50.0
63
+ 49.0
64
+ 59.0
65
+ 47.0
66
+ 58.0
67
+ 46.0
68
+ 47.0
69
+ 54.0
70
+ 45.0
71
+ 41.0
72
+ 53.0
73
+ 53.0
74
+ 48.0
75
+ 47.0
76
+ 42.0
77
+ 43.0
78
+ 56.0
79
+ 44.0
80
+ 45.0
81
+ 53.0
82
+ 52.0
83
+ 51.0
84
+ 48.0
85
+ 49.0
86
+ 51.0
87
+ 52.0
88
+ 43.0
89
+ 39.0
90
+ 42.0
91
+ 58.0
92
+ 44.0
93
+ 51.0
94
+ 46.0
95
+ 50.0
96
+ 57.0
97
+ 48.0
98
+ 49.0
99
+ 60.0
100
+ 46.0
101
+ 47.0
102
+ 47.0
103
+ 49.0
104
+ 42.0
105
+ 47.0
106
+ 51.0
107
+ 42.0
108
+ 48.0
109
+ 50.0
110
+ 42.0
111
+ 41.0
112
+ 50.0
113
+ 39.0
114
+ 55.0
๋ชจ์ˆ˜ํƒ์ •๋‹จ_์ œ์•ˆ์„œ.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ac79521fc0e43b7f144b53c0aada854cfc8186062c676285645280c590cb709b
3
+ size 2663276
์•„์ด๋””์–ด/์™ธ์ ‘์ตœ๋Œ€์›.txt ADDED
@@ -0,0 +1,316 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+
3
+ const PolygonCircleFinder = () => {
4
+ const canvasRef = useRef(null);
5
+ const [polygon, setPolygon] = useState([
6
+ { x: 200, y: 100 },
7
+ { x: 350, y: 150 },
8
+ { x: 400, y: 300 },
9
+ { x: 250, y: 350 },
10
+ { x: 100, y: 250 }
11
+ ]);
12
+ const [minCircle, setMinCircle] = useState(null);
13
+ const [maxInnerCircle, setMaxInnerCircle] = useState(null);
14
+ const [draggingPoint, setDraggingPoint] = useState(null);
15
+
16
+ // ์ตœ์†Œ ์™ธ์ ‘์› ์ฐพ๊ธฐ (Welzl's algorithm)
17
+ const findMinEnclosingCircle = (points) => {
18
+ const shuffled = [...points].sort(() => Math.random() - 0.5);
19
+ return welzl(shuffled, [], shuffled.length);
20
+ };
21
+
22
+ const welzl = (P, R, n) => {
23
+ if (n === 0 || R.length === 3) {
24
+ return makeCircle(R);
25
+ }
26
+
27
+ const p = P[n - 1];
28
+ const circle = welzl(P, R, n - 1);
29
+
30
+ if (circle && isInside(circle, p)) {
31
+ return circle;
32
+ }
33
+
34
+ return welzl(P, [...R, p], n - 1);
35
+ };
36
+
37
+ const makeCircle = (points) => {
38
+ if (points.length === 0) return null;
39
+ if (points.length === 1) return { x: points[0].x, y: points[0].y, r: 0 };
40
+ if (points.length === 2) {
41
+ const center = {
42
+ x: (points[0].x + points[1].x) / 2,
43
+ y: (points[0].y + points[1].y) / 2
44
+ };
45
+ const r = distance(points[0], points[1]) / 2;
46
+ return { ...center, r };
47
+ }
48
+
49
+ // 3์ ์œผ๋กœ ์› ๋งŒ๋“ค๊ธฐ
50
+ return circleFrom3Points(points[0], points[1], points[2]);
51
+ };
52
+
53
+ const circleFrom3Points = (p1, p2, p3) => {
54
+ const ax = p1.x, ay = p1.y;
55
+ const bx = p2.x, by = p2.y;
56
+ const cx = p3.x, cy = p3.y;
57
+
58
+ const d = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));
59
+ if (Math.abs(d) < 1e-10) return null;
60
+
61
+ const ux = ((ax * ax + ay * ay) * (by - cy) + (bx * bx + by * by) * (cy - ay) + (cx * cx + cy * cy) * (ay - by)) / d;
62
+ const uy = ((ax * ax + ay * ay) * (cx - bx) + (bx * bx + by * by) * (ax - cx) + (cx * cx + cy * cy) * (bx - ax)) / d;
63
+
64
+ const r = distance({ x: ux, y: uy }, p1);
65
+ return { x: ux, y: uy, r };
66
+ };
67
+
68
+ const distance = (p1, p2) => {
69
+ return Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2);
70
+ };
71
+
72
+ const isInside = (circle, point) => {
73
+ if (!circle) return false;
74
+ return distance(circle, point) <= circle.r + 1e-10;
75
+ };
76
+
77
+ // ์ ์ด ํด๋ฆฌ๊ณค ๋‚ด๋ถ€์— ์žˆ๋Š”์ง€ ํ™•์ธ
78
+ const isPointInPolygon = (point, polygon) => {
79
+ let inside = false;
80
+ for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
81
+ const xi = polygon[i].x, yi = polygon[i].y;
82
+ const xj = polygon[j].x, yj = polygon[j].y;
83
+
84
+ const intersect = ((yi > point.y) !== (yj > point.y))
85
+ && (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);
86
+ if (intersect) inside = !inside;
87
+ }
88
+ return inside;
89
+ };
90
+
91
+ // ์ ์—์„œ ํด๋ฆฌ๊ณค๊นŒ์ง€์˜ ์ตœ๋‹จ ๊ฑฐ๋ฆฌ
92
+ const distanceToPolygon = (point, polygon) => {
93
+ let minDist = Infinity;
94
+
95
+ for (let i = 0; i < polygon.length; i++) {
96
+ const j = (i + 1) % polygon.length;
97
+ const dist = distanceToSegment(point, polygon[i], polygon[j]);
98
+ minDist = Math.min(minDist, dist);
99
+ }
100
+
101
+ return minDist;
102
+ };
103
+
104
+ const distanceToSegment = (p, v, w) => {
105
+ const l2 = distance(v, w) ** 2;
106
+ if (l2 === 0) return distance(p, v);
107
+
108
+ let t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2;
109
+ t = Math.max(0, Math.min(1, t));
110
+
111
+ const projection = {
112
+ x: v.x + t * (w.x - v.x),
113
+ y: v.y + t * (w.y - v.y)
114
+ };
115
+
116
+ return distance(p, projection);
117
+ };
118
+
119
+ // ์™ธ์ ‘์› ์•ˆ์—์„œ ํด๋ฆฌ๊ณค ์™ธ๋ถ€์˜ ์ตœ๋Œ€ ์› ์ฐพ๊ธฐ
120
+ const findMaxInnerCircle = (enclosingCircle, polygon) => {
121
+ if (!enclosingCircle) return null;
122
+
123
+ let maxCircle = null;
124
+ let maxRadius = 0;
125
+
126
+ // ๊ทธ๋ฆฌ๋“œ ์ƒ˜ํ”Œ๋ง์œผ๋กœ ์ตœ์  ์œ„์น˜ ์ฐพ๊ธฐ
127
+ const samples = 50;
128
+ const step = (enclosingCircle.r * 2) / samples;
129
+
130
+ for (let i = 0; i <= samples; i++) {
131
+ for (let j = 0; j <= samples; j++) {
132
+ const x = enclosingCircle.x - enclosingCircle.r + i * step;
133
+ const y = enclosingCircle.y - enclosingCircle.r + j * step;
134
+ const point = { x, y };
135
+
136
+ // ์™ธ์ ‘์› ์•ˆ์— ์žˆ๋Š”์ง€ ํ™•์ธ
137
+ if (distance(point, enclosingCircle) > enclosingCircle.r) continue;
138
+
139
+ // ํด๋ฆฌ๊ณค ์™ธ๋ถ€์— ์žˆ๋Š”์ง€ ํ™•์ธ
140
+ if (isPointInPolygon(point, polygon)) continue;
141
+
142
+ // ํด๋ฆฌ๊ณค๊นŒ์ง€์˜ ๊ฑฐ๋ฆฌ์™€ ์™ธ์ ‘์› ๊ฒฝ๊ณ„๊นŒ์ง€์˜ ๊ฑฐ๋ฆฌ ์ค‘ ์ž‘์€ ๊ฐ’
143
+ const distToPoly = distanceToPolygon(point, polygon);
144
+ const distToEnclosing = enclosingCircle.r - distance(point, enclosingCircle);
145
+ const radius = Math.min(distToPoly, distToEnclosing);
146
+
147
+ if (radius > maxRadius) {
148
+ maxRadius = radius;
149
+ maxCircle = { x, y, r: radius };
150
+ }
151
+ }
152
+ }
153
+
154
+ return maxCircle;
155
+ };
156
+
157
+ useEffect(() => {
158
+ const mec = findMinEnclosingCircle(polygon);
159
+ setMinCircle(mec);
160
+
161
+ if (mec) {
162
+ const mic = findMaxInnerCircle(mec, polygon);
163
+ setMaxInnerCircle(mic);
164
+ }
165
+ }, [polygon]);
166
+
167
+ useEffect(() => {
168
+ const canvas = canvasRef.current;
169
+ if (!canvas) return;
170
+
171
+ const ctx = canvas.getContext('2d');
172
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
173
+
174
+ // ์ตœ์†Œ ์™ธ์ ‘์› ๊ทธ๋ฆฌ๊ธฐ
175
+ if (minCircle) {
176
+ ctx.strokeStyle = '#3b82f6';
177
+ ctx.lineWidth = 2;
178
+ ctx.beginPath();
179
+ ctx.arc(minCircle.x, minCircle.y, minCircle.r, 0, 2 * Math.PI);
180
+ ctx.stroke();
181
+
182
+ ctx.fillStyle = '#3b82f6';
183
+ ctx.font = '14px sans-serif';
184
+ ctx.fillText(`์™ธ์ ‘์› r=${minCircle.r.toFixed(1)}`, minCircle.x - 60, minCircle.y - minCircle.r - 10);
185
+ }
186
+
187
+ // ํด๋ฆฌ๊ณค ๊ทธ๋ฆฌ๊ธฐ
188
+ ctx.fillStyle = 'rgba(139, 92, 246, 0.3)';
189
+ ctx.strokeStyle = '#8b5cf6';
190
+ ctx.lineWidth = 2;
191
+ ctx.beginPath();
192
+ ctx.moveTo(polygon[0].x, polygon[0].y);
193
+ for (let i = 1; i < polygon.length; i++) {
194
+ ctx.lineTo(polygon[i].x, polygon[i].y);
195
+ }
196
+ ctx.closePath();
197
+ ctx.fill();
198
+ ctx.stroke();
199
+
200
+ // ํด๋ฆฌ๊ณค ๊ผญ์ง“์  ๊ทธ๋ฆฌ๊ธฐ
201
+ polygon.forEach((point, i) => {
202
+ ctx.fillStyle = '#8b5cf6';
203
+ ctx.beginPath();
204
+ ctx.arc(point.x, point.y, 6, 0, 2 * Math.PI);
205
+ ctx.fill();
206
+ });
207
+
208
+ // ์ตœ๋Œ€ ๋‚ด์ ‘์› ๊ทธ๋ฆฌ๊ธฐ
209
+ if (maxInnerCircle) {
210
+ ctx.strokeStyle = '#10b981';
211
+ ctx.fillStyle = 'rgba(16, 185, 129, 0.2)';
212
+ ctx.lineWidth = 2;
213
+ ctx.beginPath();
214
+ ctx.arc(maxInnerCircle.x, maxInnerCircle.y, maxInnerCircle.r, 0, 2 * Math.PI);
215
+ ctx.fill();
216
+ ctx.stroke();
217
+
218
+ ctx.fillStyle = '#10b981';
219
+ ctx.font = '14px sans-serif';
220
+ ctx.fillText(`์ตœ๋Œ€์› r=${maxInnerCircle.r.toFixed(1)}`, maxInnerCircle.x - 50, maxInnerCircle.y + maxInnerCircle.r + 20);
221
+ }
222
+ }, [polygon, minCircle, maxInnerCircle]);
223
+
224
+ const handleMouseDown = (e) => {
225
+ const canvas = canvasRef.current;
226
+ const rect = canvas.getBoundingClientRect();
227
+ const x = e.clientX - rect.left;
228
+ const y = e.clientY - rect.top;
229
+
230
+ for (let i = 0; i < polygon.length; i++) {
231
+ if (distance({ x, y }, polygon[i]) < 10) {
232
+ setDraggingPoint(i);
233
+ break;
234
+ }
235
+ }
236
+ };
237
+
238
+ const handleMouseMove = (e) => {
239
+ if (draggingPoint === null) return;
240
+
241
+ const canvas = canvasRef.current;
242
+ const rect = canvas.getBoundingClientRect();
243
+ const x = e.clientX - rect.left;
244
+ const y = e.clientY - rect.top;
245
+
246
+ const newPolygon = [...polygon];
247
+ newPolygon[draggingPoint] = { x, y };
248
+ setPolygon(newPolygon);
249
+ };
250
+
251
+ const handleMouseUp = () => {
252
+ setDraggingPoint(null);
253
+ };
254
+
255
+ const resetPolygon = () => {
256
+ setPolygon([
257
+ { x: 200, y: 100 },
258
+ { x: 350, y: 150 },
259
+ { x: 400, y: 300 },
260
+ { x: 250, y: 350 },
261
+ { x: 100, y: 250 }
262
+ ]);
263
+ };
264
+
265
+ return (
266
+ <div className="w-full max-w-4xl mx-auto p-6 bg-gray-50 rounded-lg">
267
+ <h1 className="text-2xl font-bold mb-4 text-gray-800">ํด๋ฆฌ๊ณค ์› ์ฐพ๊ธฐ</h1>
268
+
269
+ <div className="mb-4 p-4 bg-white rounded-lg shadow">
270
+ <h2 className="text-lg font-semibold mb-2">์„ค๋ช…:</h2>
271
+ <ul className="text-sm text-gray-700 space-y-1">
272
+ <li>โ€ข <span className="text-blue-600 font-semibold">ํŒŒ๋ž€์ƒ‰ ์›</span>: ํด๋ฆฌ๊ณค์„ ํฌํ•จํ•˜๋Š” ์ตœ์†Œ ์™ธ์ ‘์›</li>
273
+ <li>โ€ข <span className="text-green-600 font-semibold">์ดˆ๋ก์ƒ‰ ์›</span>: ์™ธ์ ‘์› ๋‚ด๋ถ€์—์„œ ํด๋ฆฌ๊ณค ์™ธ๋ถ€์— ๋“ค์–ด๊ฐ€๋Š” ์ตœ๋Œ€ ์›</li>
274
+ <li>โ€ข ํด๋ฆฌ๊ณค์˜ ๊ผญ์ง“์ ์„ ๋“œ๋ž˜๊ทธํ•˜์—ฌ ๋ชจ์–‘์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค</li>
275
+ </ul>
276
+ </div>
277
+
278
+ <div className="bg-white rounded-lg shadow-lg p-4 mb-4">
279
+ <canvas
280
+ ref={canvasRef}
281
+ width={600}
282
+ height={500}
283
+ className="border border-gray-300 rounded cursor-move"
284
+ onMouseDown={handleMouseDown}
285
+ onMouseMove={handleMouseMove}
286
+ onMouseUp={handleMouseUp}
287
+ onMouseLeave={handleMouseUp}
288
+ />
289
+ </div>
290
+
291
+ <div className="flex gap-4">
292
+ <button
293
+ onClick={resetPolygon}
294
+ className="px-6 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition"
295
+ >
296
+ ์ดˆ๊ธฐํ™”
297
+ </button>
298
+
299
+ {minCircle && (
300
+ <div className="flex-1 p-3 bg-white rounded-lg shadow text-sm">
301
+ <div className="mb-1">
302
+ <span className="font-semibold">์™ธ์ ‘์›:</span> ์ค‘์‹ฌ({minCircle.x.toFixed(1)}, {minCircle.y.toFixed(1)}), ๋ฐ˜์ง€๋ฆ„ {minCircle.r.toFixed(1)}
303
+ </div>
304
+ {maxInnerCircle && (
305
+ <div>
306
+ <span className="font-semibold">์ตœ๋Œ€์›:</span> ์ค‘์‹ฌ({maxInnerCircle.x.toFixed(1)}, {maxInnerCircle.y.toFixed(1)}), ๋ฐ˜์ง€๋ฆ„ {maxInnerCircle.r.toFixed(1)}
307
+ </div>
308
+ )}
309
+ </div>
310
+ )}
311
+ </div>
312
+ </div>
313
+ );
314
+ };
315
+
316
+ export default PolygonCircleFinder;