+
+
+
+
+
+
+
+
+
Admin 0 – Countries
+
+
+
+
+
+
About
+
Countries distinguish between metropolitan (homeland) and independent and semi-independent portions of sovereign states. If you want to see the dependent overseas regions broken out (like in ISO codes, see France for example), use map units instead.
+
Each country is coded with a world region that roughly follows the United Nations setup .
+
Includes some thematic data from the United Nations, U.S. Central Intelligence Agency, and elsewhere.
+
Disclaimer
+
Natural Earth Vector draws boundaries of countries according to defacto status. We show who actually controls the situation on the ground. Please feel free to mashup our disputed areas (link) theme to match your particular political outlook.
+
Known Problems
+
None.
+
Version History
+
+
+
The master changelog is available on Github »
+
+
+
+
+
+ This entry was posted
+ on Monday, September 21st, 2009 at 10:21 am and is filed under 110m-cultural-vectors .
+ You can follow any responses to this entry through the RSS 2.0 feed.
+
+ Both comments and pings are currently closed.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/ne_110m_admin_0_countries.cpg b/examples/ne_110m_admin_0_countries.cpg
new file mode 100644
index 00000000..7edc66b0
--- /dev/null
+++ b/examples/ne_110m_admin_0_countries.cpg
@@ -0,0 +1 @@
+UTF-8
diff --git a/examples/ne_110m_admin_0_countries.dbf b/examples/ne_110m_admin_0_countries.dbf
new file mode 100755
index 00000000..e0acd066
Binary files /dev/null and b/examples/ne_110m_admin_0_countries.dbf differ
diff --git a/examples/ne_110m_admin_0_countries.prj b/examples/ne_110m_admin_0_countries.prj
new file mode 100755
index 00000000..40dd8c6c
--- /dev/null
+++ b/examples/ne_110m_admin_0_countries.prj
@@ -0,0 +1 @@
+GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.017453292519943295]]
diff --git a/examples/ne_110m_admin_0_countries.shp b/examples/ne_110m_admin_0_countries.shp
new file mode 100755
index 00000000..9318e45c
Binary files /dev/null and b/examples/ne_110m_admin_0_countries.shp differ
diff --git a/examples/ne_110m_admin_0_countries.shx b/examples/ne_110m_admin_0_countries.shx
new file mode 100755
index 00000000..c3728e0d
Binary files /dev/null and b/examples/ne_110m_admin_0_countries.shx differ
diff --git a/examples/owid-co2-data.csv b/examples/owid-co2-data.csv
new file mode 100644
index 00000000..f6f5e1a3
--- /dev/null
+++ b/examples/owid-co2-data.csv
@@ -0,0 +1,6133 @@
+year,iso_code,co2_per_capita
+1995,AFG,0.086
+1995,,4.096
+1995,ALB,0.636
+1995,DZA,3.398
+1995,AND,6.752
+1995,AGO,0.907
+1995,AIA,6.091
+1995,ATA,0
+1995,ATG,3.963
+1995,ARG,3.584
+1995,ARM,1.012
+1995,ABW,9.175
+1995,AUS,16.945
+1995,AUT,8.059
+1995,AZE,4.222
+1995,BHS,6.14
+1995,BHR,24.184
+1995,BGD,0.179
+1995,BRB,4.891
+1995,BLR,5.822
+1995,BEL,12.475
+1995,BLZ,1.854
+1995,BEN,0.158
+1995,BMU,8.931
+1995,BTN,0.472
+1995,BOL,1.15
+1995,BES,4.706
+1995,BIH,0.907
+1995,BWA,1.972
+1995,BRA,1.643
+1995,VGB,4.97
+1995,BRN,19.476
+1995,BGR,6.831
+1995,BFA,0.06
+1995,BDI,0.04
+1995,KHM,0.142
+1995,CMR,0.407
+1995,CAN,16.792
+1995,CPV,0.481
+1995,CALF,0.064
+1995,TCD,0.062
+1995,CHL,2.883
+1995,CHN,2.76
+1995,CXR,0
+1995,COL,1.652
+1995,COM,0.159
+1995,COG,1.176
+1995,COK,2.682
+1995,CRI,1.344
+1995,CIV,0.435
+1995,HRV,3.498
+1995,CUB,2.09
+1995,CUW,30.84
+1995,CYP,6.797
+1995,CZE,12.764
+1995,COD,0.072
+1995,DNK,11.777
+1995,DJI,0.495
+1995,DMA,1.164
+1995,DOM,1.92
+1995,TLS,0.0
+1995,ECU,2.032
+1995,EGY,1.543
+1995,SLV,0.87
+1995,GNQ,2.188
+1995,ERI,0.381
+1995,EST,12.432
+1995,SWZ,1.114
+1995,ETH,0.044
+1995,FRO,13.041
+1995,FJI,0.93
+1995,FIN,11.378
+1995,FRA,6.73
+1995,PYF,2.042
+1995,GAB,4.653
+1995,GMB,0.174
+1995,GEO,0.47
+1995,DEU,11.586
+1995,GHA,0.265
+1995,GRC,8.106
+1995,GRL,9.575
+1995,GRD,1.443
+1995,GTM,0.683
+1995,GIN,0.156
+1995,GNB,0.164
+1995,GUY,2.086
+1995,HTI,0.115
+1995,HND,0.66
+1995,HKG,5.008
+1995,HUN,5.965
+1995,ISL,9.225
+1995,IND,0.789
+1995,IDN,1.122
+1995,IRN,4.429
+1995,IRQ,3.804
+1995,IRL,10.016
+1995,ISR,9.102
+1995,ITA,7.901
+1995,JAM,3.656
+1995,JPN,9.878
+1995,JOR,2.963
+1995,KAZ,10.24
+1995,KEN,0.281
+1995,KIR,0.36
+1995,KWT,33.909
+1995,KGZ,0.981
+1995,LAO,0.136
+1995,LVA,3.554
+1995,LBN,3.095
+1995,ALSO,0.883
+1995,LBR,0.194
+1995,LBY,11.535
+1995,LIE,6.606
+1995,LTU,4.018
+1995,LUX,22.461
+1995,MAC,3.104
+1995,MDG,0.086
+1995,MWI,0.09
+1995,MYS,5.504
+1995,MDV,0.965
+1995,MLI,0.06
+1995,MLT,6.422
+1995,MHL,1.734
+1995,MRT,0.426
+1995,MUS,1.576
+1995,MEX,3.681
+1995,FSM,1.627
+1995,MDA,2.557
+1995,MCO,0
+1995,MNG,3.363
+1995,MNE,2.1
+1995,MSR,3.942
+1995,MAR,1.081
+1995,MOZ,0.073
+1995,MMR,0.161
+1995,NAME,1.009
+1995,NRU,10.28
+1995,NPL,0.104
+1995,NLD,11.213
+1995,NCL,10.33
+1995,NZL,7.622
+1995,NIC,0.583
+1995,NER,0.057
+1995,NGA,0.78
+1995,NIU,3.102
+1995,PRK,3.399
+1995,MKD,3.995
+1995,NOR,8.822
+1995,OMN,8.623
+1995,PAK,0.628
+1995,PLW,11.481
+1995,PSE,0.36
+1995,PAN,1.345
+1995,PNG,0.446
+1995,PRY,0.855
+1995,PER,1.028
+1995,PHL,0.866
+1995,POL,9.443
+1995,PRT,5.419
+1995,QAT,64.326
+1995,ROU,5.579
+1995,RUS,10.903
+1995,RWA,0.08
+1995,SHN,1.726
+1995,KNA,3.012
+1995,LCA,1.804
+1995,SPM,10.812
+1995,VCT,1.123
+1995,WSM,0.649
+1995,SMR,0
+1995,STP,0.358
+1995,SAU,14.195
+1995,SEN,0.397
+1995,SRB,4.903
+1995,SYC,2.647
+1995,SLE,0.052
+1995,SGP,11.441
+1995,SXM,16.265
+1995,SVK,8.251
+1995,SVN,7.711
+1995,SLB,0.508
+1995,SOME,0.081
+1995,ZAF,8.223
+1995,KOR,8.452
+1995,SSD,0.067
+1995,ESP,6.682
+1995,LKA,0.32
+1995,SDN,0.17
+1995,SURE,4.742
+1995,SWE,6.794
+1995,CHE,6.167
+1995,SYR,3.22
+1995,TWN,7.871
+1995,TJK,0.413
+1995,TZA,0.08
+1995,THA,2.586
+1995,TGO,0.274
+1995,TON,0.953
+1995,TO,11.265
+1995,TUN,1.751
+1995,TUR,3.058
+1995,TKM,8.179
+1995,TCA,4.603
+1995,TUV,0.381
+1995,UGA,0.045
+1995,UKR,7.639
+1995,ARE,29.771
+1995,GBR,9.773
+1995,USA,20.422
+1995,URY,1.42
+1995,UZB,4.551
+1995,VUT,0.387
+1995,VAT,0
+1995,VEN,5.642
+1995,VNM,0.408
+1995,WLF,1.29
+1995,YEM,0.687
+1995,ZMB,0.247
+1995,ZWE,1.367
+1996,AFG,0.08
+1996,,4.163
+1996,ALB,0.617
+1996,DZA,3.451
+1996,AND,7.081
+1996,AGO,1.06
+1996,AIA,6.301
+1996,ATA,0
+1996,ATG,4.146
+1996,ARG,3.705
+1996,ARM,0.758
+1996,ABW,9.133
+1996,AUS,17.128
+1996,AUT,8.468
+1996,AZE,3.93
+1996,BHS,5.948
+1996,BHR,25.315
+1996,BGD,0.179
+1996,BRB,5.095
+1996,BLR,5.915
+1996,BEL,12.79
+1996,BLZ,1.467
+1996,BEN,0.191
+1996,BMU,8.815
+1996,BTN,0.555
+1996,BOL,1.254
+1996,BES,4.355
+1996,BIH,1.085
+1996,BWA,1.741
+1996,BRA,1.742
+1996,VGB,5.452
+1996,BRN,19.198
+1996,BGR,6.958
+1996,BFA,0.066
+1996,BDI,0.041
+1996,KHM,0.146
+1996,CMR,0.44
+1996,CAN,17.172
+1996,CPV,0.513
+1996,CALF,0.063
+1996,TCD,0.062
+1996,CHL,3.3
+1996,CHN,2.857
+1996,CXR,0
+1996,COL,1.636
+1996,COM,0.163
+1996,COG,1.433
+1996,COK,2.878
+1996,CRI,1.282
+1996,CIV,0.508
+1996,HRV,3.645
+1996,CUB,2.128
+1996,CUW,29.169
+1996,CYP,7.058
+1996,CZE,13.114
+1996,COD,0.079
+1996,DNK,14.25
+1996,DJI,0.477
+1996,DMA,1.059
+1996,DOM,2.057
+1996,TLS,0.0
+1996,ECU,2.149
+1996,EGY,1.494
+1996,SLV,0.761
+1996,GNQ,2.327
+1996,ERI,0.397
+1996,EST,13.273
+1996,SWZ,0.809
+1996,ETH,0.047
+1996,FRO,13.606
+1996,FJI,0.995
+1996,FIN,12.495
+1996,FRA,6.992
+1996,PYF,1.994
+1996,GAB,5.115
+1996,GMB,0.172
+1996,GEO,0.851
+1996,DEU,11.804
+1996,GHA,0.272
+1996,GRC,8.248
+1996,GRL,10.663
+1996,GRD,1.468
+1996,GTM,0.618
+1996,GIN,0.161
+1996,GNB,0.18
+1996,GUY,2.193
+1996,HTI,0.131
+1996,HND,0.648
+1996,HKG,4.564
+1996,HUN,6.127
+1996,ISL,9.377
+1996,IND,0.838
+1996,IDN,1.257
+1996,IRN,4.463
+1996,IRQ,3.453
+1996,IRL,10.389
+1996,ISR,9.254
+1996,ITA,7.797
+1996,JAM,3.825
+1996,JPN,9.955
+1996,JOR,3.0
+1996,KAZ,9.57
+1996,KEN,0.336
+1996,KIR,0.354
+1996,KWT,30.31
+1996,KGZ,1.211
+1996,LAO,0.149
+1996,LVA,3.635
+1996,LBN,3.161
+1996,ALSO,0.888
+1996,LBR,0.194
+1996,LBY,11.193
+1996,LIE,6.567
+1996,LTU,4.229
+1996,LUX,22.283
+1996,MAC,3.48
+1996,MDG,0.087
+1996,MWI,0.089
+1996,MYS,5.347
+1996,MDV,1.097
+1996,MLI,0.064
+1996,MLT,6.577
+1996,MHL,1.706
+1996,MRT,0.437
+1996,MUS,1.662
+1996,MEX,3.732
+1996,FSM,1.653
+1996,MDA,2.638
+1996,MCO,0
+1996,MNG,3.367
+1996,MNE,2.453
+1996,MSR,4.736
+1996,MAR,1.094
+1996,MOZ,0.068
+1996,MMR,0.166
+1996,NAME,1.044
+1996,NRU,9.893
+1996,NPL,0.103
+1996,NLD,11.732
+1996,NCL,10.7
+1996,NZL,7.86
+1996,NIC,0.592
+1996,NER,0.063
+1996,NGA,0.882
+1996,NIU,3.155
+1996,PRK,2.931
+1996,MKD,4.722
+1996,NOR,9.477
+1996,OMN,8.194
+1996,PAK,0.682
+1996,PLW,11.141
+1996,PSE,0.379
+1996,PAN,1.604
+1996,PNG,0.458
+1996,PRY,0.789
+1996,PER,0.992
+1996,PHL,0.864
+1996,POL,9.825
+1996,PRT,5.131
+1996,QAT,65.039
+1996,ROU,5.761
+1996,RUS,10.705
+1996,RWA,0.069
+1996,SHN,1.741
+1996,KNA,3.059
+1996,LCA,1.904
+1996,SPM,10.817
+1996,VCT,1.154
+1996,WSM,0.705
+1996,SMR,0
+1996,STP,0.352
+1996,SAU,13.566
+1996,SEN,0.414
+1996,SRB,5.733
+1996,SYC,3.103
+1996,SLE,0.053
+1996,SGP,13.147
+1996,SXM,15.305
+1996,SVK,8.216
+1996,SVN,8.037
+1996,SLB,0.512
+1996,SOME,0.077
+1996,ZAF,8.144
+1996,KOR,9.22
+1996,SSD,0.068
+1996,ESP,6.338
+1996,LKA,0.38
+1996,SDN,0.171
+1996,SURE,4.692
+1996,SWE,7.214
+1996,CHE,6.238
+1996,SYR,3.204
+1996,TWN,8.18
+1996,TJK,0.468
+1996,TZA,0.081
+1996,THA,2.883
+1996,TGO,0.285
+1996,TON,0.768
+1996,TO,14.053
+1996,TUN,1.762
+1996,TUR,3.311
+1996,TKM,7.36
+1996,TCA,4.398
+1996,TUV,0.38
+1996,UGA,0.048
+1996,UKR,6.95
+1996,ARE,29.312
+1996,GBR,10.103
+1996,USA,20.867
+1996,URY,1.675
+1996,UZB,4.61
+1996,VUT,0.482
+1996,VAT,0
+1996,VEN,5.239
+1996,VNM,0.473
+1996,WLF,1.536
+1996,YEM,0.68
+1996,ZMB,0.206
+1996,ZWE,1.334
+1997,AFG,0.073
+1997,,4.13
+1997,ALB,0.474
+1997,DZA,2.998
+1997,AND,7.192
+1997,AGO,1.068
+1997,AIA,6.136
+1997,ATA,0
+1997,ATG,4.372
+1997,ARG,3.812
+1997,ARM,0.994
+1997,ABW,9.264
+1997,AUS,17.4
+1997,AUT,8.445
+1997,AZE,3.73
+1997,BHS,5.186
+1997,BHR,26.49
+1997,BGD,0.189
+1997,BRB,5.852
+1997,BLR,5.921
+1997,BEL,12.21
+1997,BLZ,1.789
+1997,BEN,0.192
+1997,BMU,8.697
+1997,BTN,0.713
+1997,BOL,1.44
+1997,BES,4.508
+1997,BIH,2.067
+1997,BWA,1.729
+1997,BRA,1.821
+1997,VGB,5.513
+1997,BRN,19.434
+1997,BGR,6.709
+1997,BFA,0.074
+1997,BDI,0.042
+1997,KHM,0.136
+1997,CMR,0.386
+1997,CAN,17.488
+1997,CPV,0.527
+1997,CALF,0.063
+1997,TCD,0.061
+1997,CHL,3.777
+1997,CHN,2.84
+1997,CXR,0
+1997,COL,1.75
+1997,COM,0.167
+1997,COG,1.68
+1997,COK,2.914
+1997,CRI,1.315
+1997,CIV,0.47
+1997,HRV,3.939
+1997,CUB,2.256
+1997,CUW,30.898
+1997,CYP,7.018
+1997,CZE,12.739
+1997,COD,0.069
+1997,DNK,12.4
+1997,DJI,0.499
+1997,DMA,1.167
+1997,DOM,2.118
+1997,TLS,0.0
+1997,ECU,1.621
+1997,EGY,1.667
+1997,SLV,0.927
+1997,GNQ,3.261
+1997,ERI,0.353
+1997,EST,13.143
+1997,SWZ,1.154
+1997,ETH,0.049
+1997,FRO,13.251
+1997,FJI,0.95
+1997,FIN,12.198
+1997,FRA,6.838
+1997,PYF,2.054
+1997,GAB,4.685
+1997,GMB,0.17
+1997,GEO,0.954
+1997,DEU,11.438
+1997,GHA,0.302
+1997,GRC,8.625
+1997,GRL,11.015
+1997,GRD,1.597
+1997,GTM,0.688
+1997,GIN,0.164
+1997,GNB,0.173
+1997,GUY,2.392
+1997,HTI,0.17
+1997,HND,0.653
+1997,HKG,4.735
+1997,HUN,5.999
+1997,ISL,9.653
+1997,IND,0.856
+1997,IDN,1.368
+1997,IRN,4.279
+1997,IRQ,3.411
+1997,IRL,10.66
+1997,ISR,9.553
+1997,ITA,7.895
+1997,JAM,3.941
+1997,JPN,9.873
+1997,JOR,2.973
+1997,KAZ,9.255
+1997,KEN,0.289
+1997,KIR,0.348
+1997,KWT,32.144
+1997,KGZ,1.174
+1997,LAO,0.154
+1997,LVA,3.474
+1997,LBN,3.676
+1997,ALSO,0.897
+1997,LBR,0.187
+1997,LBY,10.562
+1997,LIE,6.868
+1997,LTU,4.104
+1997,LUX,20.468
+1997,MAC,3.613
+1997,MDG,0.104
+1997,MWI,0.087
+1997,MYS,5.63
+1997,MDV,1.226
+1997,MLI,0.07
+1997,MLT,6.574
+1997,MHL,1.679
+1997,MRT,0.434
+1997,MUS,1.686
+1997,MEX,3.892
+1997,FSM,1.747
+1997,MDA,1.662
+1997,MCO,0
+1997,MNG,3.197
+1997,MNE,2.635
+1997,MSR,4.65
+1997,MAR,1.094
+1997,MOZ,0.071
+1997,MMR,0.169
+1997,NAME,1.045
+1997,NRU,9.878
+1997,NPL,0.114
+1997,NLD,11.215
+1997,NCL,8.72
+1997,NZL,8.284
+1997,NIC,0.636
+1997,NER,0.061
+1997,NGA,0.842
+1997,NIU,3.211
+1997,PRK,2.79
+1997,MKD,4.214
+1997,NOR,9.449
+1997,OMN,8.04
+1997,PAK,0.664
+1997,PLW,10.798
+1997,PSE,0.304
+1997,PAN,1.637
+1997,PNG,0.527
+1997,PRY,0.857
+1997,PER,1.094
+1997,PHL,0.964
+1997,POL,9.564
+1997,PRT,5.392
+1997,QAT,76.612
+1997,ROU,5.301
+1997,RUS,10.019
+1997,RWA,0.063
+1997,SHN,1.758
+1997,KNA,3.271
+1997,LCA,1.909
+1997,SPM,7.425
+1997,VCT,1.154
+1997,WSM,0.698
+1997,SMR,0
+1997,STP,0.346
+1997,SAU,11.118
+1997,SEN,0.352
+1997,SRB,6.16
+1997,SYC,3.59
+1997,SLE,0.047
+1997,SGP,15.087
+1997,SXM,16.13
+1997,SVK,8.218
+1997,SVN,8.296
+1997,SLB,0.517
+1997,SOME,0.069
+1997,ZAF,8.507
+1997,KOR,9.681
+1997,SSD,0.08
+1997,ESP,6.616
+1997,LKA,0.408
+1997,SDN,0.205
+1997,SURE,4.647
+1997,SWE,6.629
+1997,CHE,6.072
+1997,SYR,3.261
+1997,TWN,8.755
+1997,TJK,0.351
+1997,TZA,0.089
+1997,THA,2.971
+1997,TGO,0.191
+1997,TON,0.983
+1997,TO,14.346
+1997,TUN,1.801
+1997,TUR,3.461
+1997,TKM,7.149
+1997,TCA,5.095
+1997,TUV,0.38
+1997,UGA,0.049
+1997,UKR,6.787
+1997,ARE,26.554
+1997,GBR,9.66
+1997,USA,20.882
+1997,URY,1.699
+1997,UZB,4.566
+1997,VUT,0.492
+1997,VAT,0
+1997,VEN,5.629
+1997,VNM,0.598
+1997,WLF,1.525
+1997,YEM,0.711
+1997,ZMB,0.258
+1997,ZWE,1.224
+1998,AFG,0.069
+1998,,4.064
+1998,ALB,0.543
+1998,DZA,3.56
+1998,AND,7.53
+1998,AGO,1.075
+1998,AIA,6.318
+1998,ATA,0
+1998,ATG,4.442
+1998,ARG,3.836
+1998,ARM,1.055
+1998,ABW,9.555
+1998,AUS,17.963
+1998,AUT,8.39
+1998,AZE,3.953
+1998,BHS,6.308
+1998,BHR,27.311
+1998,BGD,0.187
+1998,BRB,5.963
+1998,BLR,5.8
+1998,BEL,12.781
+1998,BLZ,1.645
+1998,BEN,0.169
+1998,BMU,8.582
+1998,BTN,0.686
+1998,BOL,1.358
+1998,BES,0.246
+1998,BIH,2.564
+1998,BWA,2.018
+1998,BRA,1.855
+1998,VGB,5.371
+1998,BRN,17.477
+1998,BGR,6.45
+1998,BFA,0.077
+1998,BDI,0.041
+1998,KHM,0.168
+1998,CMR,0.375
+1998,CAN,17.591
+1998,CPV,0.549
+1998,CALF,0.062
+1998,TCD,0.06
+1998,CHL,3.818
+1998,CHN,2.699
+1998,CXR,0
+1998,COL,1.734
+1998,COM,0.178
+1998,COG,1.317
+1998,COK,2.787
+1998,CRI,1.37
+1998,CIV,0.426
+1998,HRV,4.08
+1998,CUB,2.202
+1998,CUW,1.728
+1998,CYP,7.208
+1998,CZE,12.238
+1998,COD,0.06
+1998,DNK,11.557
+1998,DJI,0.554
+1998,DMA,1.116
+1998,DOM,2.148
+1998,TLS,0.071
+1998,ECU,1.893
+1998,EGY,1.827
+1998,SLV,0.974
+1998,GNQ,3.656
+1998,ERI,0.252
+1998,EST,12.005
+1998,SWZ,1.156
+1998,ETH,0.05
+1998,FRO,13.876
+1998,FJI,0.913
+1998,FIN,11.517
+1998,FRA,7.127
+1998,PYF,2.006
+1998,GAB,5.334
+1998,GMB,0.178
+1998,GEO,1.093
+1998,DEU,11.327
+1998,GHA,0.298
+1998,GRC,9.015
+1998,GRL,10.637
+1998,GRD,1.656
+1998,GTM,0.772
+1998,GIN,0.171
+1998,GNB,0.145
+1998,GUY,2.416
+1998,HTI,0.153
+1998,HND,0.763
+1998,HKG,5.946
+1998,HUN,5.975
+1998,ISL,9.62
+1998,IND,0.857
+1998,IDN,1.176
+1998,IRN,4.783
+1998,IRQ,3.507
+1998,IRL,11.066
+1998,ISR,9.543
+1998,ITA,8.108
+1998,JAM,3.762
+1998,JPN,9.535
+1998,JOR,2.942
+1998,KAZ,9.324
+1998,KEN,0.342
+1998,KIR,0.342
+1998,KWT,29.025
+1998,KGZ,1.227
+1998,LAO,0.156
+1998,LVA,3.374
+1998,LBN,3.897
+1998,ALSO,0.908
+1998,LBR,0.179
+1998,LBY,10.37
+1998,LIE,7.117
+1998,LTU,4.364
+1998,LUX,18.092
+1998,MAC,3.718
+1998,MDG,0.106
+1998,MWI,0.084
+1998,MYS,5.033
+1998,MDV,1.097
+1998,MLI,0.088
+1998,MLT,6.433
+1998,MHL,1.722
+1998,MRT,0.419
+1998,MUS,1.815
+1998,MEX,4.024
+1998,FSM,1.809
+1998,MDA,1.48
+1998,MCO,0
+1998,MNG,3.169
+1998,MNE,2.773
+1998,MSR,4.555
+1998,MAR,1.111
+1998,MOZ,0.066
+1998,MMR,0.181
+1998,NAME,1.047
+1998,NRU,9.512
+1998,NPL,0.094
+1998,NLD,11.209
+1998,NCL,8.507
+1998,NZL,7.837
+1998,NIC,0.682
+1998,NER,0.064
+1998,NGA,0.741
+1998,NIU,3.286
+1998,PRK,2.552
+1998,MKD,4.793
+1998,NOR,9.436
+1998,OMN,8.091
+1998,PAK,0.665
+1998,PLW,10.486
+1998,PSE,0.495
+1998,PAN,2.045
+1998,PNG,0.687
+1998,PRY,0.89
+1998,PER,1.087
+1998,PHL,0.916
+1998,POL,8.835
+1998,PRT,5.813
+1998,QAT,63.49
+1998,ROU,4.732
+1998,RUS,9.922
+1998,RWA,0.061
+1998,SHN,1.778
+1998,KNA,3.394
+1998,LCA,2.079
+1998,SPM,8.635
+1998,VCT,1.412
+1998,WSM,0.792
+1998,SMR,0
+1998,STP,0.341
+1998,SAU,10.412
+1998,SEN,0.36
+1998,SRB,6.497
+1998,SYC,3.726
+1998,SLE,0.049
+1998,SGP,12.263
+1998,SXM,0.897
+1998,SVK,8.16
+1998,SVN,8.067
+1998,SLB,0.53
+1998,SOME,0.063
+1998,ZAF,8.228
+1998,KOR,8.174
+1998,SSD,0.068
+1998,ESP,6.803
+1998,LKA,0.417
+1998,SDN,0.174
+1998,SURE,4.558
+1998,SWE,6.66
+1998,CHE,6.276
+1998,SYR,3.429
+1998,TWN,9.135
+1998,TJK,0.406
+1998,TZA,0.076
+1998,THA,2.587
+1998,TGO,0.273
+1998,TON,0.868
+1998,TO,15.05
+1998,TUN,1.838
+1998,TUR,3.408
+1998,TKM,7.547
+1998,TCA,5.093
+1998,TUV,1.138
+1998,UGA,0.054
+1998,UKR,6.61
+1998,ARE,27.423
+1998,GBR,9.729
+1998,USA,20.785
+1998,URY,1.728
+1998,UZB,4.987
+1998,VUT,0.46
+1998,VAT,0
+1998,VEN,7.248
+1998,VNM,0.621
+1998,WLF,1.515
+1998,YEM,0.729
+1998,ZMB,0.243
+1998,ZWE,1.224
+1999,AFG,0.057
+1999,,4.093
+1999,ALB,0.931
+1999,DZA,3.014
+1999,AND,7.811
+1999,AGO,1.088
+1999,AIA,6.138
+1999,ATA,0
+1999,ATG,4.665
+1999,ARG,4.029
+1999,ARM,0.948
+1999,ABW,9.321
+1999,AUS,18.295
+1999,AUT,8.218
+1999,AZE,3.558
+1999,BHS,6.349
+1999,BHR,26.461
+1999,BGD,0.198
+1999,BRB,6.259
+1999,BLR,5.623
+1999,BEL,12.195
+1999,BLZ,1.496
+1999,BEN,0.215
+1999,BMU,8.409
+1999,BTN,0.676
+1999,BOL,1.296
+1999,BES,1.823
+1999,BIH,2.488
+1999,BWA,1.867
+1999,BRA,1.886
+1999,VGB,5.792
+1999,BRN,18.278
+1999,BGR,5.675
+1999,BFA,0.079
+1999,BDI,0.041
+1999,KHM,0.159
+1999,CMR,0.355
+1999,CAN,17.911
+1999,CPV,0.644
+1999,CALF,0.063
+1999,TCD,0.06
+1999,CHL,4.046
+1999,CHN,2.834
+1999,CXR,0
+1999,COL,1.438
+1999,COM,0.181
+1999,COG,1.298
+1999,COK,3.105
+1999,CRI,1.394
+1999,CIV,0.373
+1999,HRV,4.348
+1999,CUB,2.21
+1999,CUW,13.128
+1999,CYP,7.362
+1999,CZE,11.384
+1999,COD,0.047
+1999,DNK,11.025
+1999,DJI,0.535
+1999,DMA,1.173
+1999,DOM,2.119
+1999,TLS,0.177
+1999,ECU,1.771
+1999,EGY,1.834
+1999,SLV,0.943
+1999,GNQ,3.834
+1999,ERI,0.261
+1999,EST,11.356
+1999,SWZ,1.216
+1999,ETH,0.048
+1999,FRO,13.933
+1999,FJI,0.919
+1999,FIN,11.396
+1999,FRA,7.067
+1999,PYF,2.182
+1999,GAB,4.847
+1999,GMB,0.197
+1999,GEO,0.981
+1999,DEU,10.979
+1999,GHA,0.293
+1999,GRC,8.908
+1999,GRL,10.599
+1999,GRD,1.818
+1999,GTM,0.768
+1999,GIN,0.175
+1999,GNB,0.161
+1999,GUY,2.417
+1999,HTI,0.155
+1999,HND,0.726
+1999,HKG,6.406
+1999,HUN,6.042
+1999,ISL,10.234
+1999,IND,0.913
+1999,IDN,1.384
+1999,IRN,5.769
+1999,IRQ,3.444
+1999,IRL,11.409
+1999,ISR,9.135
+1999,ITA,8.19
+1999,JAM,3.836
+1999,JPN,9.806
+1999,JOR,2.882
+1999,KAZ,7.817
+1999,KEN,0.336
+1999,KIR,0.336
+1999,KWT,29.284
+1999,KGZ,0.952
+1999,LAO,0.158
+1999,LVA,3.181
+1999,LBN,3.866
+1999,ALSO,0.915
+1999,LBR,0.135
+1999,LBY,9.937
+1999,LIE,6.942
+1999,LTU,3.701
+1999,LUX,18.907
+1999,MAC,3.565
+1999,MDG,0.112
+1999,MWI,0.081
+1999,MYS,4.649
+1999,MDV,1.515
+1999,MLI,0.089
+1999,MLT,6.497
+1999,MHL,1.633
+1999,MRT,0.418
+1999,MUS,2.012
+1999,MEX,4.01
+1999,FSM,1.708
+1999,MDA,1.088
+1999,MCO,0
+1999,MNG,3.083
+1999,MNE,1.912
+1999,MSR,6.328
+1999,MAR,1.135
+1999,MOZ,0.067
+1999,MMR,0.198
+1999,NAME,0.935
+1999,NRU,8.803
+1999,NPL,0.132
+1999,NLD,10.805
+1999,NCL,9.467
+1999,NZL,8.208
+1999,NIC,0.712
+1999,NER,0.061
+1999,NGA,0.71
+1999,NIU,3.382
+1999,PRK,2.764
+1999,MKD,4.34
+1999,NOR,9.545
+1999,OMN,9.507
+1999,PAK,0.664
+1999,PLW,10.198
+1999,PSE,0.449
+1999,PAN,1.91
+1999,PNG,0.48
+1999,PRY,0.871
+1999,PER,1.126
+1999,PHL,0.894
+1999,POL,8.585
+1999,PRT,6.531
+1999,QAT,59.102
+1999,ROU,4.039
+1999,RUS,10.141
+1999,RWA,0.063
+1999,SHN,1.798
+1999,KNA,3.594
+1999,LCA,2.13
+1999,SPM,8.698
+1999,VCT,1.446
+1999,WSM,0.744
+1999,SMR,0
+1999,STP,0.336
+1999,SAU,11.018
+1999,SEN,0.38
+1999,SRB,4.496
+1999,SYC,3.857
+1999,SLE,0.039
+1999,SGP,12.404
+1999,SXM,6.774
+1999,SVK,8.008
+1999,SVN,7.76
+1999,SLB,0.516
+1999,SOME,0.058
+1999,ZAF,8.086
+1999,KOR,8.824
+1999,SSD,0.072
+1999,ESP,7.352
+1999,LKA,0.456
+1999,SDN,0.185
+1999,SURE,4.512
+1999,SWE,6.326
+1999,CHE,6.222
+1999,SYR,3.468
+1999,TWN,9.421
+1999,TJK,0.407
+1999,TZA,0.073
+1999,THA,2.678
+1999,TGO,0.38
+1999,TON,1.078
+1999,TO,17.026
+1999,TUN,1.904
+1999,TUR,3.291
+1999,TKM,8.842
+1999,TCA,5.494
+1999,TUV,1.138
+1999,UGA,0.054
+1999,UKR,6.054
+1999,ARE,24.96
+1999,GBR,9.579
+1999,USA,20.789
+1999,URY,2.038
+1999,UZB,5.023
+1999,VUT,0.469
+1999,VAT,0
+1999,VEN,5.969
+1999,VNM,0.621
+1999,WLF,1.755
+1999,YEM,0.811
+1999,ZMB,0.185
+1999,ZWE,1.342
+2000,AFG,0.054
+2000,,4.147
+2000,ALB,0.951
+2000,DZA,2.775
+2000,AND,7.925
+2000,AGO,0.976
+2000,AIA,6.95
+2000,ATA,0
+2000,ATG,4.734
+2000,ARG,3.851
+2000,ARM,1.102
+2000,ABW,26.683
+2000,AUS,18.404
+2000,AUT,8.261
+2000,AZE,3.634
+2000,BHS,6.234
+2000,BHR,27.035
+2000,BGD,0.205
+2000,BRB,6.608
+2000,BLR,5.353
+2000,BEL,12.346
+2000,BLZ,1.646
+2000,BEN,0.21
+2000,BMU,8.415
+2000,BTN,0.676
+2000,BOL,0.982
+2000,BES,4.783
+2000,BIH,3.279
+2000,BWA,2.187
+2000,BRA,1.934
+2000,VGB,5.826
+2000,BRN,15.957
+2000,BGR,5.608
+2000,BFA,0.087
+2000,BDI,0.043
+2000,KHM,0.163
+2000,CMR,0.37
+2000,CAN,18.482
+2000,CPV,0.648
+2000,CALF,0.062
+2000,TCD,0.059
+2000,CHL,3.811
+2000,CHN,2.887
+2000,CXR,0
+2000,COL,1.443
+2000,COM,0.191
+2000,COG,1.367
+2000,COK,3.222
+2000,CRI,1.356
+2000,CIV,0.395
+2000,HRV,4.322
+2000,CUB,2.321
+2000,CUW,35.283
+2000,CYP,7.492
+2000,CZE,12.432
+2000,COD,0.035
+2000,DNK,10.169
+2000,DJI,0.494
+2000,DMA,1.501
+2000,DOM,2.224
+2000,TLS,0.25
+2000,ECU,1.657
+2000,EGY,2.015
+2000,SLV,0.952
+2000,GNQ,3.941
+2000,ERI,0.253
+2000,EST,11.083
+2000,SWZ,1.173
+2000,ETH,0.052
+2000,FRO,15.078
+2000,FJI,0.979
+2000,FIN,11.014
+2000,FRA,6.929
+2000,PYF,2.438
+2000,GAB,4.814
+2000,GMB,0.191
+2000,GEO,1.055
+2000,DEU,11.023
+2000,GHA,0.27
+2000,GRC,9.329
+2000,GRL,11.881
+2000,GRD,1.773
+2000,GTM,0.832
+2000,GIN,0.179
+2000,GNB,0.119
+2000,GUY,2.302
+2000,HTI,0.195
+2000,HND,0.756
+2000,HKG,5.984
+2000,HUN,5.735
+2000,ISL,10.421
+2000,IND,0.923
+2000,IDN,1.314
+2000,IRN,5.558
+2000,IRQ,3.381
+2000,IRL,12.006
+2000,ISR,9.73
+2000,ITA,8.26
+2000,JAM,3.948
+2000,JPN,9.966
+2000,JOR,3.006
+2000,KAZ,9.41
+2000,KEN,0.337
+2000,KIR,0.412
+2000,KWT,28.368
+2000,KGZ,0.927
+2000,LAO,0.177
+2000,LVA,2.96
+2000,LBN,3.58
+2000,ALSO,0.926
+2000,LBR,0.138
+2000,LBY,10.328
+2000,LIE,6.561
+2000,LTU,3.29
+2000,LUX,19.979
+2000,MAC,3.775
+2000,MDG,0.12
+2000,MWI,0.076
+2000,MYS,5.355
+2000,MDV,1.595
+2000,MLI,0.095
+2000,MLT,6.183
+2000,MHL,1.824
+2000,MRT,0.413
+2000,MUS,2.212
+2000,MEX,4.002
+2000,FSM,1.607
+2000,MDA,0.84
+2000,MCO,0
+2000,MNG,3.032
+2000,MNE,2.401
+2000,MSR,4.965
+2000,MAR,1.163
+2000,MOZ,0.074
+2000,MMR,0.224
+2000,NAME,0.882
+2000,NRU,8.103
+2000,NPL,0.124
+2000,NLD,10.819
+2000,NCL,10.022
+2000,NZL,8.364
+2000,NIC,0.726
+2000,NER,0.059
+2000,NGA,0.788
+2000,NIU,3.496
+2000,PRK,2.96
+2000,MKD,4.155
+2000,NOR,9.376
+2000,OMN,10.191
+2000,PAK,0.673
+2000,PLW,10.577
+2000,PSE,0.529
+2000,PAN,1.908
+2000,PNG,0.533
+2000,PRY,0.704
+2000,PER,1.076
+2000,PHL,0.928
+2000,POL,8.245
+2000,PRT,6.37
+2000,QAT,62.411
+2000,ROU,4.258
+2000,RUS,10.073
+2000,RWA,0.064
+2000,SHN,1.817
+2000,KNA,3.785
+2000,LCA,2.182
+2000,SPM,8.724
+2000,VCT,1.288
+2000,WSM,0.777
+2000,SMR,0
+2000,STP,0.331
+2000,SAU,14.031
+2000,SEN,0.402
+2000,SRB,5.665
+2000,SYC,3.935
+2000,SLE,0.059
+2000,SGP,11.939
+2000,SXM,18.108
+2000,SVK,7.651
+2000,SVN,7.586
+2000,SLB,0.52
+2000,SOME,0.055
+2000,ZAF,8.081
+2000,KOR,9.404
+2000,SSD,0.075
+2000,ESP,7.611
+2000,LKA,0.54
+2000,SDN,0.201
+2000,SURE,4.577
+2000,SWE,6.192
+2000,CHE,6.073
+2000,SYR,3.322
+2000,TWN,10.223
+2000,TJK,0.356
+2000,TZA,0.075
+2000,THA,2.654
+2000,TGO,0.266
+2000,TON,0.928
+2000,TO,18.29
+2000,TUN,1.977
+2000,TUR,3.586
+2000,TKM,8.615
+2000,TCA,5.856
+2000,TUV,0.758
+2000,UGA,0.057
+2000,UKR,5.844
+2000,ARE,34.138
+2000,GBR,9.669
+2000,USA,21.282
+2000,URY,1.6
+2000,UZB,4.954
+2000,VUT,0.458
+2000,VAT,0
+2000,VEN,5.847
+2000,VNM,0.683
+2000,WLF,1.739
+2000,YEM,0.844
+2000,ZMB,0.18
+2000,ZWE,1.168
+2001,AFG,0.054
+2001,,4.121
+2001,ALB,1.021
+2001,DZA,2.783
+2001,AND,7.723
+2001,AGO,0.939
+2001,AIA,7.414
+2001,ATA,0
+2001,ATG,4.855
+2001,ARG,3.586
+2001,ARM,1.128
+2001,ABW,26.54
+2001,AUS,18.588
+2001,AUT,8.728
+2001,AZE,3.431
+2001,BHS,5.902
+2001,BHR,21.054
+2001,BGD,0.236
+2001,BRB,6.225
+2001,BLR,5.294
+2001,BEL,12.228
+2001,BLZ,1.802
+2001,BEN,0.234
+2001,BMU,8.54
+2001,BTN,0.637
+2001,BOL,1.0
+2001,BES,4.775
+2001,BIH,3.162
+2001,BWA,2.184
+2001,BRA,1.942
+2001,VGB,6.023
+2001,BRN,15.23
+2001,BGR,6.089
+2001,BFA,0.081
+2001,BDI,0.032
+2001,KHM,0.182
+2001,CMR,0.359
+2001,CAN,18.035
+2001,CPV,0.747
+2001,CALF,0.064
+2001,TCD,0.059
+2001,CHL,3.409
+2001,CHN,2.93
+2001,CXR,0
+2001,COL,1.427
+2001,COM,0.194
+2001,COG,1.284
+2001,COK,3.109
+2001,CRI,1.398
+2001,CIV,0.444
+2001,HRV,4.611
+2001,CUB,2.264
+2001,CUW,36.13
+2001,CYP,7.233
+2001,CZE,12.428
+2001,COD,0.033
+2001,DNK,10.427
+2001,DJI,0.479
+2001,DMA,1.612
+2001,DOM,2.208
+2001,TLS,0.133
+2001,ECU,1.822
+2001,EGY,1.752
+2001,SLV,0.978
+2001,GNQ,4.302
+2001,ERI,0.254
+2001,EST,11.442
+2001,SWZ,1.052
+2001,ETH,0.062
+2001,FRO,16.471
+2001,FJI,1.239
+2001,FIN,12.05
+2001,FRA,6.965
+2001,PYF,2.844
+2001,GAB,4.959
+2001,GMB,0.203
+2001,GEO,0.899
+2001,DEU,11.228
+2001,GHA,0.296
+2001,GRC,9.516
+2001,GRL,10.964
+2001,GRD,1.799
+2001,GTM,0.852
+2001,GIN,0.185
+2001,GNB,0.119
+2001,GUY,2.295
+2001,HTI,0.173
+2001,HND,0.828
+2001,HKG,5.558
+2001,HUN,5.906
+2001,ISL,10.045
+2001,IND,0.918
+2001,IDN,1.46
+2001,IRN,5.792
+2001,IRQ,3.789
+2001,IRL,12.433
+2001,ISR,10.103
+2001,ITA,8.256
+2001,JAM,4.028
+2001,JPN,9.831
+2001,JOR,3.023
+2001,KAZ,9.081
+2001,KEN,0.292
+2001,KIR,0.405
+2001,KWT,29.649
+2001,KGZ,0.774
+2001,LAO,0.193
+2001,LVA,3.178
+2001,LBN,3.738
+2001,ALSO,0.938
+2001,LBR,0.14
+2001,LBY,10.067
+2001,LIE,6.427
+2001,LTU,3.537
+2001,LUX,20.862
+2001,MAC,3.838
+2001,MDG,0.104
+2001,MWI,0.067
+2001,MYS,5.56
+2001,MDV,1.607
+2001,MLI,0.1
+2001,MLT,6.805
+2001,MHL,1.885
+2001,MRT,0.425
+2001,MUS,2.335
+2001,MEX,4.102
+2001,FSM,1.473
+2001,MDA,0.901
+2001,MCO,0
+2001,MNG,3.156
+2001,MNE,2.634
+2001,MSR,5.426
+2001,MAR,1.272
+2001,MOZ,0.085
+2001,MMR,0.192
+2001,NAME,1.084
+2001,NRU,7.762
+2001,NPL,0.13
+2001,NLD,11.084
+2001,NCL,8.304
+2001,NZL,8.839
+2001,NIC,0.755
+2001,NER,0.054
+2001,NGA,0.794
+2001,NIU,3.596
+2001,PRK,3.048
+2001,MKD,4.036
+2001,NOR,9.636
+2001,OMN,9.435
+2001,PAK,0.662
+2001,PLW,10.889
+2001,PSE,0.417
+2001,PAN,2.271
+2001,PNG,0.585
+2001,PRY,0.709
+2001,PER,0.941
+2001,PHL,0.883
+2001,POL,8.109
+2001,PRT,6.282
+2001,QAT,67.494
+2001,ROU,4.541
+2001,RUS,10.364
+2001,RWA,0.064
+2001,SHN,1.841
+2001,KNA,3.822
+2001,LCA,2.236
+2001,SPM,8.747
+2001,VCT,1.58
+2001,WSM,0.829
+2001,SMR,0
+2001,STP,0.351
+2001,SAU,13.706
+2001,SEN,0.428
+2001,SRB,6.227
+2001,SYC,4.238
+2001,SLE,0.076
+2001,SGP,11.938
+2001,SXM,18.493
+2001,SVK,8.04
+2001,SVN,8.223
+2001,SLB,0.532
+2001,SOME,0.055
+2001,ZAF,7.868
+2001,KOR,9.681
+2001,SSD,0.083
+2001,ESP,7.611
+2001,LKA,0.546
+2001,SDN,0.223
+2001,SURE,4.848
+2001,SWE,6.276
+2001,CHE,6.24
+2001,SYR,3.046
+2001,TWN,10.289
+2001,TJK,0.357
+2001,TZA,0.086
+2001,THA,2.706
+2001,TGO,0.225
+2001,TON,0.852
+2001,TO,19.989
+2001,TUN,2.044
+2001,TUR,3.282
+2001,TKM,7.343
+2001,TCA,5.795
+2001,TUV,1.14
+2001,UGA,0.057
+2001,UKR,6.277
+2001,ARE,29.361
+2001,GBR,9.781
+2001,USA,20.695
+2001,URY,1.524
+2001,UZB,4.999
+2001,VUT,0.465
+2001,VAT,0
+2001,VEN,5.246
+2001,VNM,0.764
+2001,WLF,1.725
+2001,YEM,0.892
+2001,ZMB,0.185
+2001,ZWE,1.05
+2002,AFG,0.064
+2002,,4.158
+2002,ALB,1.2
+2002,DZA,2.868
+2002,AND,7.497
+2002,AGO,0.918
+2002,AIA,7.275
+2002,ATA,0
+2002,ATG,5.267
+2002,ARG,3.285
+2002,ARM,0.991
+2002,ABW,26.543
+2002,AUS,18.615
+2002,AUT,8.909
+2002,AZE,3.413
+2002,BHS,6.099
+2002,BHR,21.205
+2002,BGD,0.238
+2002,BRB,6.221
+2002,BLR,5.312
+2002,BEL,12.251
+2002,BLZ,1.689
+2002,BEN,0.284
+2002,BMU,8.96
+2002,BTN,0.673
+2002,BOL,1.169
+2002,BES,4.442
+2002,BIH,3.376
+2002,BWA,2.215
+2002,BRA,1.927
+2002,VGB,6.706
+2002,BRN,13.44
+2002,BGR,5.781
+2002,BFA,0.079
+2002,BDI,0.032
+2002,KHM,0.176
+2002,CMR,0.336
+2002,CAN,18.018
+2002,CPV,0.805
+2002,CALF,0.062
+2002,TCD,0.06
+2002,CHL,3.494
+2002,CHN,3.203
+2002,CXR,0
+2002,COL,1.382
+2002,COM,0.19
+2002,COG,0.817
+2002,COK,2.678
+2002,CRI,1.515
+2002,CIV,0.421
+2002,HRV,4.882
+2002,CUB,2.285
+2002,CUW,34.647
+2002,CYP,7.299
+2002,CZE,12.109
+2002,COD,0.034
+2002,DNK,10.329
+2002,DJI,0.506
+2002,DMA,1.502
+2002,DOM,2.382
+2002,TLS,0.288
+2002,ECU,1.899
+2002,EGY,1.736
+2002,SLV,1.01
+2002,GNQ,3.662
+2002,ERI,0.236
+2002,EST,11.144
+2002,SWZ,1.029
+2002,ETH,0.062
+2002,FRO,15.489
+2002,FJI,1.035
+2002,FIN,12.506
+2002,FRA,6.845
+2002,PYF,2.8
+2002,GAB,4.659
+2002,GMB,0.195
+2002,GEO,0.824
+2002,DEU,11.036
+2002,GHA,0.312
+2002,GRC,9.462
+2002,GRL,10.251
+2002,GRD,1.896
+2002,GTM,0.872
+2002,GIN,0.189
+2002,GNB,0.12
+2002,GUY,2.255
+2002,HTI,0.206
+2002,HND,0.847
+2002,HKG,5.765
+2002,HUN,5.822
+2002,ISL,10.398
+2002,IND,0.93
+2002,IDN,1.401
+2002,IRN,5.843
+2002,IRQ,3.677
+2002,IRL,11.833
+2002,ISR,9.329
+2002,ITA,8.37
+2002,JAM,3.867
+2002,JPN,10.045
+2002,JOR,3.113
+2002,KAZ,10.27
+2002,KEN,0.241
+2002,KIR,0.397
+2002,KWT,29.299
+2002,KGZ,0.978
+2002,LAO,0.208
+2002,LVA,3.233
+2002,LBN,3.653
+2002,ALSO,0.954
+2002,LBR,0.141
+2002,LBY,9.717
+2002,LIE,6.525
+2002,LTU,3.603
+2002,LUX,22.382
+2002,MAC,3.373
+2002,MDG,0.072
+2002,MWI,0.073
+2002,MYS,5.469
+2002,MDV,2.031
+2002,MLI,0.101
+2002,MLT,6.788
+2002,MHL,2.016
+2002,MRT,0.443
+2002,MUS,2.337
+2002,MEX,4.068
+2002,FSM,1.341
+2002,MDA,0.977
+2002,MCO,0
+2002,MNG,3.286
+2002,MNE,2.791
+2002,MSR,10.294
+2002,MAR,1.273
+2002,MOZ,0.082
+2002,MMR,0.201
+2002,NAME,0.947
+2002,NRU,7.416
+2002,NPL,0.102
+2002,NLD,10.99
+2002,NCL,10.383
+2002,NZL,8.748
+2002,NIC,0.759
+2002,NER,0.055
+2002,NGA,0.694
+2002,NIU,3.682
+2002,PRK,2.904
+2002,MKD,3.794
+2002,NOR,9.376
+2002,OMN,11.167
+2002,PAK,0.692
+2002,PLW,10.692
+2002,PSE,0.349
+2002,PAN,1.865
+2002,PNG,0.622
+2002,PRY,0.73
+2002,PER,0.931
+2002,PHL,0.864
+2002,POL,7.921
+2002,PRT,6.669
+2002,QAT,63.19
+2002,ROU,4.586
+2002,RUS,10.35
+2002,RWA,0.062
+2002,SHN,1.871
+2002,KNA,4.274
+2002,LCA,2.219
+2002,SPM,9.359
+2002,VCT,1.647
+2002,WSM,0.864
+2002,SMR,0
+2002,STP,0.391
+2002,SAU,14.674
+2002,SEN,0.436
+2002,SRB,6.608
+2002,SYC,4.222
+2002,SLE,0.098
+2002,SGP,11.275
+2002,SXM,17.727
+2002,SVK,7.81
+2002,SVN,8.35
+2002,SLB,0.545
+2002,SOME,0.059
+2002,ZAF,7.48
+2002,KOR,10.06
+2002,SSD,0.101
+2002,ESP,8.003
+2002,LKA,0.573
+2002,SDN,0.276
+2002,SURE,3.147
+2002,SWE,6.351
+2002,CHE,5.973
+2002,SYR,2.351
+2002,TWN,10.562
+2002,TJK,0.287
+2002,TZA,0.096
+2002,THA,2.875
+2002,TGO,0.25
+2002,TON,0.988
+2002,TO,21.324
+2002,TUN,2.049
+2002,TUR,3.352
+2002,TKM,6.412
+2002,TCA,7.285
+2002,TUV,1.141
+2002,UGA,0.057
+2002,UKR,6.168
+2002,ARE,23.296
+2002,GBR,9.439
+2002,USA,20.622
+2002,URY,1.378
+2002,UZB,5.183
+2002,VUT,0.417
+2002,VAT,0
+2002,VEN,6.48
+2002,VNM,0.862
+2002,WLF,1.708
+2002,YEM,0.843
+2002,ZMB,0.187
+2002,ZWE,0.993
+2003,AFG,0.069
+2003,,4.324
+2003,ALB,1.391
+2003,DZA,2.95
+2003,AND,7.236
+2003,AGO,0.965
+2003,AIA,7.477
+2003,ATA,0
+2003,ATG,5.489
+2003,ARG,3.501
+2003,ARM,1.12
+2003,ABW,27.624
+2003,AUS,18.754
+2003,AUT,9.532
+2003,AZE,3.603
+2003,BHS,6.116
+2003,BHR,21.547
+2003,BGD,0.245
+2003,BRB,6.353
+2003,BLR,5.473
+2003,BEL,12.329
+2003,BLZ,1.638
+2003,BEN,0.313
+2003,BMU,8.971
+2003,BTN,0.593
+2003,BOL,1.211
+2003,BES,4.263
+2003,BIH,3.432
+2003,BWA,2.094
+2003,BRA,1.887
+2003,VGB,6.661
+2003,BRN,15.811
+2003,BGR,6.375
+2003,BFA,0.082
+2003,BDI,0.023
+2003,KHM,0.186
+2003,CMR,0.349
+2003,CAN,18.388
+2003,CPV,0.862
+2003,CALF,0.056
+2003,TCD,0.096
+2003,CHL,3.474
+2003,CHN,3.756
+2003,CXR,0
+2003,COL,1.397
+2003,COM,0.238
+2003,COG,0.951
+2003,COK,3.154
+2003,CRI,1.572
+2003,CIV,0.293
+2003,HRV,5.203
+2003,CUB,2.316
+2003,CUW,34.252
+2003,CYP,7.557
+2003,CZE,12.448
+2003,COD,0.039
+2003,DNK,11.238
+2003,DJI,0.523
+2003,DMA,1.713
+2003,DOM,2.393
+2003,TLS,0.268
+2003,ECU,2.035
+2003,EGY,1.974
+2003,SLV,1.068
+2003,GNQ,3.968
+2003,ERI,0.268
+2003,EST,12.595
+2003,SWZ,0.948
+2003,ETH,0.067
+2003,FRO,15.455
+2003,FJI,1.208
+2003,FIN,13.937
+2003,FRA,6.906
+2003,PYF,2.965
+2003,GAB,4.894
+2003,GMB,0.189
+2003,GEO,0.929
+2003,DEU,11.062
+2003,GHA,0.313
+2003,GRC,9.817
+2003,GRL,11.442
+2003,GRD,1.988
+2003,GTM,0.831
+2003,GIN,0.194
+2003,GNB,0.148
+2003,GUY,2.442
+2003,HTI,0.193
+2003,HND,0.928
+2003,HKG,6.274
+2003,HUN,6.114
+2003,ISL,10.303
+2003,IND,0.948
+2003,IDN,1.521
+2003,IRN,5.99
+2003,IRQ,3.741
+2003,IRL,11.54
+2003,ISR,9.638
+2003,ITA,8.637
+2003,JAM,4.024
+2003,JPN,10.096
+2003,JOR,3.147
+2003,KAZ,11.407
+2003,KEN,0.198
+2003,KIR,0.427
+2003,KWT,29.663
+2003,KGZ,1.059
+2003,LAO,0.215
+2003,LVA,3.368
+2003,LBN,4.113
+2003,ALSO,0.969
+2003,LBR,0.151
+2003,LBY,10.009
+2003,LIE,6.741
+2003,LTU,3.649
+2003,LUX,23.158
+2003,MAC,3.311
+2003,MDG,0.095
+2003,MWI,0.076
+2003,MYS,6.2
+2003,MDV,1.701
+2003,MLI,0.101
+2003,MLT,7.276
+2003,MHL,1.949
+2003,MRT,0.447
+2003,MUS,2.463
+2003,MEX,4.258
+2003,FSM,1.442
+2003,MDA,1.076
+2003,MCO,0
+2003,MNG,3.152
+2003,MNE,2.988
+2003,MSR,7.876
+2003,MAR,1.233
+2003,MOZ,0.097
+2003,MMR,0.213
+2003,NAME,0.983
+2003,NRU,6.363
+2003,NPL,0.109
+2003,NLD,11.155
+2003,NCL,11.613
+2003,NZL,9.015
+2003,NIC,0.814
+2003,NER,0.058
+2003,NGA,0.752
+2003,NIU,1.877
+2003,PRK,2.949
+2003,MKD,4.118
+2003,NOR,9.616
+2003,OMN,14.129
+2003,PAK,0.704
+2003,PLW,10.677
+2003,PSE,0.378
+2003,PAN,1.914
+2003,PNG,0.666
+2003,PRY,0.753
+2003,PER,0.893
+2003,PHL,0.847
+2003,POL,8.258
+2003,PRT,6.154
+2003,QAT,62.141
+2003,ROU,4.833
+2003,RUS,10.604
+2003,RWA,0.059
+2003,SHN,1.905
+2003,KNA,4.259
+2003,LCA,2.314
+2003,SPM,10.001
+2003,VCT,1.749
+2003,WSM,0.84
+2003,SMR,0
+2003,STP,0.429
+2003,SAU,14.427
+2003,SEN,0.472
+2003,SRB,7.077
+2003,SYC,4.075
+2003,SLE,0.1
+2003,SGP,11.697
+2003,SXM,17.567
+2003,SVK,7.871
+2003,SVN,8.179
+2003,SLB,0.556
+2003,SOME,0.057
+2003,ZAF,8.407
+2003,KOR,10.206
+2003,SSD,0.108
+2003,ESP,7.964
+2003,LKA,0.567
+2003,SDN,0.301
+2003,SURE,3.052
+2003,SWE,6.393
+2003,CHE,6.09
+2003,SYR,3.122
+2003,TWN,10.987
+2003,TJK,0.31
+2003,TZA,0.099
+2003,THA,2.95
+2003,TGO,0.333
+2003,TON,1.123
+2003,TO,23.931
+2003,TUN,2.074
+2003,TUR,3.541
+2003,TKM,8.524
+2003,TCA,7.239
+2003,TUV,1.134
+2003,UGA,0.057
+2003,UKR,6.454
+2003,ARE,27.913
+2003,GBR,9.583
+2003,USA,20.646
+2003,URY,1.368
+2003,UZB,5.03
+2003,VUT,0.407
+2003,VAT,0
+2003,VEN,5.983
+2003,VNM,0.948
+2003,WLF,1.703
+2003,YEM,0.915
+2003,ZMB,0.191
+2003,ZWE,0.879
+2004,AFG,0.053
+2004,,4.42
+2004,ALB,1.364
+2004,DZA,2.821
+2004,AND,7.285
+2004,AGO,0.906
+2004,AIA,8.58
+2004,ATA,0
+2004,ATG,5.708
+2004,ARG,4.053
+2004,ARM,1.204
+2004,ABW,27.963
+2004,AUS,19.216
+2004,AUT,9.509
+2004,AZE,3.77
+2004,BHS,5.97
+2004,BHR,21.457
+2004,BGD,0.259
+2004,BRB,6.406
+2004,BLR,5.826
+2004,BEL,12.315
+2004,BLZ,1.441
+2004,BEN,0.329
+2004,BMU,9.283
+2004,BTN,0.474
+2004,BOL,1.197
+2004,BES,4.288
+2004,BIH,3.732
+2004,BWA,2.095
+2004,BRA,1.957
+2004,VGB,6.767
+2004,BRN,15.648
+2004,BGR,6.297
+2004,BFA,0.082
+2004,BDI,0.028
+2004,KHM,0.188
+2004,CMR,0.339
+2004,CAN,18.165
+2004,CPV,0.873
+2004,CALF,0.053
+2004,TCD,0.094
+2004,CHL,3.698
+2004,CHN,4.023
+2004,CXR,0
+2004,COL,1.312
+2004,COM,0.252
+2004,COG,1.002
+2004,COK,3.631
+2004,CRI,1.609
+2004,CIV,0.398
+2004,HRV,5.15
+2004,CUB,2.247
+2004,CUW,35.449
+2004,CYP,7.646
+2004,CZE,12.503
+2004,COD,0.037
+2004,DNK,10.174
+2004,DJI,0.497
+2004,DMA,2.083
+2004,DOM,1.917
+2004,TLS,0.525
+2004,ECU,2.162
+2004,EGY,1.968
+2004,SLV,1.036
+2004,GNQ,7.427
+2004,ERI,0.278
+2004,EST,12.718
+2004,SWZ,0.939
+2004,ETH,0.069
+2004,FRO,15.569
+2004,FJI,1.547
+2004,FIN,13.186
+2004,FRA,6.882
+2004,PYF,2.88
+2004,GAB,4.507
+2004,GMB,0.198
+2004,GEO,1.071
+2004,DEU,10.898
+2004,GHA,0.294
+2004,GRC,9.85
+2004,GRL,11.241
+2004,GRD,1.873
+2004,GTM,0.87
+2004,GIN,0.197
+2004,GNB,0.15
+2004,GUY,2.534
+2004,HTI,0.184
+2004,HND,0.974
+2004,HKG,6.011
+2004,HUN,5.969
+2004,ISL,10.637
+2004,IND,0.99
+2004,IDN,1.518
+2004,IRN,6.338
+2004,IRQ,4.049
+2004,IRL,11.45
+2004,ISR,8.896
+2004,ITA,8.67
+2004,JAM,3.969
+2004,JPN,10.047
+2004,JOR,3.375
+2004,KAZ,12.006
+2004,KEN,0.218
+2004,KIR,0.419
+2004,KWT,30.476
+2004,KGZ,1.131
+2004,LAO,0.222
+2004,LVA,3.416
+2004,LBN,3.762
+2004,ALSO,0.998
+2004,LBR,0.175
+2004,LBY,9.996
+2004,LIE,6.683
+2004,LTU,3.873
+2004,LUX,25.823
+2004,MAC,3.606
+2004,MDG,0.099
+2004,MWI,0.073
+2004,MYS,6.698
+2004,MDV,2.207
+2004,MLI,0.11
+2004,MLT,6.969
+2004,MHL,2.153
+2004,MRT,0.47
+2004,MUS,2.47
+2004,MEX,4.221
+2004,FSM,1.282
+2004,MDA,1.14
+2004,MCO,0
+2004,MNG,3.35
+2004,MNE,3.227
+2004,MSR,10.173
+2004,MAR,1.406
+2004,MOZ,0.095
+2004,MMR,0.265
+2004,NAME,1.016
+2004,NRU,6.368
+2004,NPL,0.099
+2004,NLD,11.224
+2004,NCL,10.538
+2004,NZL,8.78
+2004,NIC,0.813
+2004,NER,0.059
+2004,NGA,0.693
+2004,NIU,1.914
+2004,PRK,2.995
+2004,MKD,3.963
+2004,NOR,9.633
+2004,OMN,11.756
+2004,PAK,0.76
+2004,PLW,10.848
+2004,PSE,0.634
+2004,PAN,1.761
+2004,PNG,0.746
+2004,PRY,0.75
+2004,PER,1.016
+2004,PHL,0.861
+2004,POL,8.389
+2004,PRT,6.409
+2004,QAT,60.827
+2004,ROU,4.851
+2004,RUS,10.696
+2004,RWA,0.059
+2004,SHN,1.941
+2004,KNA,4.56
+2004,LCA,2.454
+2004,SPM,9.477
+2004,VCT,1.952
+2004,WSM,0.877
+2004,SMR,0
+2004,STP,0.465
+2004,SAU,16.948
+2004,SEN,0.49
+2004,SRB,7.651
+2004,SYC,4.367
+2004,SLE,0.094
+2004,SGP,10.973
+2004,SXM,18.248
+2004,SVK,7.961
+2004,SVN,8.366
+2004,SLB,0.582
+2004,SOME,0.055
+2004,ZAF,9.253
+2004,KOR,10.296
+2004,SSD,0.13
+2004,ESP,8.217
+2004,LKA,0.623
+2004,SDN,0.376
+2004,SURE,3.047
+2004,SWE,6.277
+2004,CHE,6.128
+2004,SYR,2.836
+2004,TWN,11.359
+2004,TJK,0.376
+2004,TZA,0.11
+2004,THA,3.175
+2004,TGO,0.313
+2004,TON,1.046
+2004,TO,24.032
+2004,TUN,2.147
+2004,TUR,3.611
+2004,TKM,10.24
+2004,TCA,7.041
+2004,TUV,1.12
+2004,UGA,0.059
+2004,UKR,6.572
+2004,ARE,27.972
+2004,GBR,9.558
+2004,USA,20.795
+2004,URY,1.679
+2004,UZB,4.921
+2004,VUT,0.276
+2004,VAT,0
+2004,VEN,5.444
+2004,VNM,1.081
+2004,WLF,1.724
+2004,YEM,0.964
+2004,ZMB,0.188
+2004,ZWE,0.775
+2005,AFG,0.077
+2005,,4.512
+2005,ALB,1.405
+2005,DZA,3.368
+2005,AND,7.205
+2005,AGO,0.791
+2005,AIA,9.048
+2005,ATA,0
+2005,ATG,6.009
+2005,ARG,4.126
+2005,ARM,1.436
+2005,ABW,28.771
+2005,AUS,19.146
+2005,AUT,9.614
+2005,AZE,3.95
+2005,BHS,5.478
+2005,BHR,21.957
+2005,BGD,0.267
+2005,BRB,6.54
+2005,BLR,5.97
+2005,BEL,11.945
+2005,BLZ,1.503
+2005,BEN,0.306
+2005,BMU,9.25
+2005,BTN,0.597
+2005,BOL,1.313
+2005,BES,4.08
+2005,BIH,3.919
+2005,BWA,2.162
+2005,BRA,1.951
+2005,VGB,7.01
+2005,BRN,14.803
+2005,BGR,6.474
+2005,BFA,0.081
+2005,BDI,0.021
+2005,KHM,0.209
+2005,CMR,0.314
+2005,CAN,17.841
+2005,CPV,0.907
+2005,CALF,0.051
+2005,TCD,0.093
+2005,CHL,3.779
+2005,CHN,4.508
+2005,CXR,0
+2005,COL,1.425
+2005,COM,0.241
+2005,COG,1.185
+2005,COK,4.107
+2005,CRI,1.558
+2005,CIV,0.399
+2005,HRV,5.269
+2005,CUB,2.331
+2005,CUW,34.614
+2005,CYP,7.673
+2005,CZE,12.227
+2005,COD,0.04
+2005,DNK,9.48
+2005,DJI,0.498
+2005,DMA,2.187
+2005,DOM,1.958
+2005,TLS,0.331
+2005,ECU,2.221
+2005,EGY,2.127
+2005,SLV,1.043
+2005,GNQ,7.179
+2005,ERI,0.272
+2005,EST,12.621
+2005,SWZ,0.947
+2005,ETH,0.064
+2005,FRO,14.94
+2005,FJI,1.237
+2005,FIN,10.874
+2005,FRA,6.877
+2005,PYF,3.055
+2005,GAB,4.267
+2005,GMB,0.194
+2005,GEO,1.266
+2005,DEU,10.657
+2005,GHA,0.272
+2005,GRC,10.248
+2005,GRL,11.309
+2005,GRD,1.96
+2005,GTM,0.922
+2005,GIN,0.2
+2005,GNB,0.154
+2005,GUY,2.132
+2005,HTI,0.189
+2005,HND,0.904
+2005,HKG,6.304
+2005,HUN,5.996
+2005,ISL,10.027
+2005,IND,1.027
+2005,IDN,1.519
+2005,IRN,6.583
+2005,IRQ,3.973
+2005,IRL,11.685
+2005,ISR,8.419
+2005,ITA,8.631
+2005,JAM,3.891
+2005,JPN,10.095
+2005,JOR,3.599
+2005,KAZ,12.777
+2005,KEN,0.239
+2005,KIR,0.485
+2005,KWT,33.294
+2005,KGZ,1.063
+2005,LAO,0.228
+2005,LVA,3.498
+2005,LBN,3.586
+2005,ALSO,1.017
+2005,LBR,0.199
+2005,LBY,10.392
+2005,LIE,6.613
+2005,LTU,4.109
+2005,LUX,25.985
+2005,MAC,3.742
+2005,MDG,0.093
+2005,MWI,0.067
+2005,MYS,6.556
+2005,MDV,1.957
+2005,MLI,0.11
+2005,MLT,6.476
+2005,MHL,2.09
+2005,MRT,0.477
+2005,MUS,2.618
+2005,MEX,4.397
+2005,FSM,1.057
+2005,MDA,1.234
+2005,MCO,0
+2005,MNG,3.327
+2005,MNE,2.766
+2005,MSR,8.544
+2005,MAR,1.468
+2005,MOZ,0.088
+2005,MMR,0.242
+2005,NAME,1.178
+2005,NRU,6.026
+2005,NPL,0.114
+2005,NLD,10.937
+2005,NCL,11.603
+2005,NZL,9.055
+2005,NIC,0.783
+2005,NER,0.05
+2005,NGA,0.722
+2005,NIU,1.948
+2005,PRK,3.112
+2005,MKD,4.135
+2005,NOR,9.357
+2005,OMN,12.562
+2005,PAK,0.772
+2005,PLW,11.073
+2005,PSE,0.774
+2005,PAN,2.109
+2005,PNG,0.766
+2005,PRY,0.69
+2005,PER,1.077
+2005,PHL,0.85
+2005,POL,8.367
+2005,PRT,6.617
+2005,QAT,56.027
+2005,ROU,4.828
+2005,RUS,10.866
+2005,RWA,0.057
+2005,SHN,1.978
+2005,KNA,4.232
+2005,LCA,2.348
+2005,SPM,10.153
+2005,VCT,1.962
+2005,WSM,0.893
+2005,SMR,0
+2005,STP,0.476
+2005,SAU,16.512
+2005,SEN,0.505
+2005,SRB,6.594
+2005,SYC,4.436
+2005,SLE,0.075
+2005,SGP,9.572
+2005,SXM,17.908
+2005,SVK,7.961
+2005,SVN,8.446
+2005,SLB,0.585
+2005,SOME,0.054
+2005,ZAF,8.491
+2005,KOR,10.413
+2005,SSD,0.119
+2005,ESP,8.432
+2005,LKA,0.607
+2005,SDN,0.356
+2005,SURE,3.077
+2005,SWE,5.952
+2005,CHE,6.163
+2005,SYR,2.731
+2005,TWN,11.689
+2005,TJK,0.352
+2005,TZA,0.136
+2005,THA,3.256
+2005,TGO,0.301
+2005,TON,1.075
+2005,TO,27.922
+2005,TUN,2.171
+2005,TUR,3.855
+2005,TKM,9.81
+2005,TCA,7.932
+2005,TUV,1.107
+2005,UGA,0.072
+2005,UKR,6.683
+2005,ARE,26.723
+2005,GBR,9.445
+2005,USA,20.658
+2005,URY,1.726
+2005,UZB,4.576
+2005,VUT,0.269
+2005,VAT,0
+2005,VEN,5.561
+2005,VNM,1.153
+2005,WLF,2.011
+2005,YEM,1.016
+2005,ZMB,0.194
+2005,ZWE,0.875
+2006,AFG,0.085
+2006,,4.608
+2006,ALB,1.302
+2006,DZA,3.167
+2006,AND,6.804
+2006,AGO,0.85
+2006,AIA,9.793
+2006,ATA,0
+2006,ATG,6.385
+2006,ARG,4.413
+2006,ARM,1.454
+2006,ABW,28.394
+2006,AUS,19.174
+2006,AUT,9.291
+2006,AZE,3.939
+2006,BHS,5.257
+2006,BHR,20.001
+2006,BGD,0.292
+2006,BRB,6.564
+2006,BLR,6.253
+2006,BEL,11.705
+2006,BLZ,1.535
+2006,BEN,0.399
+2006,BMU,10.337
+2006,BTN,0.584
+2006,BOL,1.292
+2006,BES,4.086
+2006,BIH,4.284
+2006,BWA,2.143
+2006,BRA,1.954
+2006,VGB,7.375
+2006,BRN,19.162
+2006,BGR,6.675
+2006,BFA,0.095
+2006,BDI,0.024
+2006,KHM,0.222
+2006,CMR,0.309
+2006,CAN,17.478
+2006,CPV,0.991
+2006,CALF,0.053
+2006,TCD,0.093
+2006,CHL,3.921
+2006,CHN,4.946
+2006,CXR,0
+2006,COL,1.457
+2006,COM,0.273
+2006,COG,1.255
+2006,COK,4.332
+2006,CRI,1.6
+2006,CIV,0.355
+2006,HRV,5.33
+2006,CUB,2.382
+2006,CUW,35.497
+2006,CYP,7.755
+2006,CZE,12.281
+2006,COD,0.04
+2006,DNK,10.902
+2006,DJI,0.489
+2006,DMA,2.078
+2006,DOM,2.059
+2006,TLS,0.318
+2006,ECU,2.119
+2006,EGY,2.222
+2006,SLV,1.113
+2006,GNQ,6.827
+2006,ERI,0.191
+2006,EST,12.211
+2006,SWZ,0.942
+2006,ETH,0.067
+2006,FRO,14.023
+2006,FJI,1.362
+2006,FIN,12.983
+2006,FRA,6.67
+2006,PYF,2.985
+2006,GAB,3.689
+2006,GMB,0.203
+2006,GEO,1.549
+2006,DEU,10.81
+2006,GHA,0.369
+2006,GRC,10.122
+2006,GRL,11.663
+2006,GRD,2.079
+2006,GTM,0.908
+2006,GIN,0.203
+2006,GNB,0.153
+2006,GUY,1.991
+2006,HTI,0.189
+2006,HND,0.994
+2006,HKG,6.022
+2006,HUN,5.937
+2006,ISL,10.378
+2006,IND,1.102
+2006,IDN,1.495
+2006,IRN,6.96
+2006,IRQ,3.41
+2006,IRL,11.241
+2006,ISR,9.097
+2006,ITA,8.505
+2006,JAM,4.304
+2006,JPN,9.907
+2006,JOR,3.379
+2006,KAZ,13.94
+2006,KEN,0.259
+2006,KIR,0.476
+2006,KWT,32.177
+2006,KGZ,1.041
+2006,LAO,0.296
+2006,LVA,3.771
+2006,LBN,3.134
+2006,ALSO,1.031
+2006,LBR,0.195
+2006,LBY,9.332
+2006,LIE,6.62
+2006,LTU,4.272
+2006,LUX,25.214
+2006,MAC,3.227
+2006,MDG,0.087
+2006,MWI,0.065
+2006,MYS,6.419
+2006,MDV,2.366
+2006,MLI,0.113
+2006,MLT,6.487
+2006,MHL,2.23
+2006,MRT,0.503
+2006,MUS,2.868
+2006,MEX,4.442
+2006,FSM,1.03
+2006,MDA,1.276
+2006,MCO,0
+2006,MNG,3.596
+2006,MNE,3.261
+2006,MSR,8.474
+2006,MAR,1.5
+2006,MOZ,0.092
+2006,MMR,0.266
+2006,NAME,1.177
+2006,NRU,4.261
+2006,NPL,0.093
+2006,NLD,10.596
+2006,NCL,11.018
+2006,NZL,8.931
+2006,NIC,0.799
+2006,NER,0.047
+2006,NGA,0.623
+2006,NIU,1.973
+2006,PRK,3.143
+2006,MKD,4.157
+2006,NOR,9.406
+2006,OMN,15.686
+2006,PAK,0.806
+2006,PLW,11.564
+2006,PSE,0.624
+2006,PAN,2.234
+2006,PNG,0.753
+2006,PRY,0.706
+2006,PER,0.994
+2006,PHL,0.757
+2006,POL,8.731
+2006,PRT,6.15
+2006,QAT,58.745
+2006,ROU,4.988
+2006,RUS,11.323
+2006,RWA,0.055
+2006,SHN,2.018
+2006,KNA,4.297
+2006,LCA,2.465
+2006,SPM,10.803
+2006,VCT,1.94
+2006,WSM,0.909
+2006,SMR,0
+2006,STP,0.508
+2006,SAU,17.3
+2006,SEN,0.393
+2006,SRB,7.805
+2006,SYC,4.416
+2006,SLE,0.1
+2006,SGP,9.43
+2006,SXM,18.479
+2006,SVK,7.916
+2006,SVN,8.531
+2006,SLB,0.587
+2006,SOME,0.052
+2006,ZAF,9.027
+2006,KOR,10.478
+2006,SSD,0.124
+2006,ESP,8.096
+2006,LKA,0.593
+2006,SDN,0.38
+2006,SURE,3.33
+2006,SWE,5.902
+2006,CHE,6.069
+2006,SYR,2.762
+2006,TWN,12.073
+2006,TJK,0.376
+2006,TZA,0.145
+2006,THA,3.254
+2006,TGO,0.255
+2006,TON,1.208
+2006,TO,31.031
+2006,TUN,2.191
+2006,TUR,4.057
+2006,TKM,9.992
+2006,TCA,8.595
+2006,TUV,1.093
+2006,UGA,0.083
+2006,UKR,7.148
+2006,ARE,24.843
+2006,GBR,9.339
+2006,USA,20.192
+2006,URY,1.986
+2006,UZB,4.639
+2006,VUT,0.214
+2006,VAT,0
+2006,VEN,5.741
+2006,VNM,1.187
+2006,WLF,2.052
+2006,YEM,1.083
+2006,ZMB,0.186
+2006,ZWE,0.841
+2007,AFG,0.108
+2007,,4.683
+2007,ALB,1.327
+2007,DZA,3.234
+2007,AND,6.889
+2007,AGO,0.841
+2007,AIA,9.93
+2007,ATA,0
+2007,ATG,6.521
+2007,ARG,4.351
+2007,ARM,1.698
+2007,ABW,29.146
+2007,AUS,19.187
+2007,AUT,8.936
+2007,AZE,3.383
+2007,BHS,5.235
+2007,BHR,25.728
+2007,BGD,0.296
+2007,BRB,6.639
+2007,BLR,6.128
+2007,BEL,11.307
+2007,BLZ,1.603
+2007,BEN,0.455
+2007,BMU,11.54
+2007,BTN,0.577
+2007,BOL,1.307
+2007,BES,4.423
+2007,BIH,4.36
+2007,BWA,2.15
+2007,BRA,2.047
+2007,VGB,7.411
+2007,BRN,21.324
+2007,BGR,7.214
+2007,BFA,0.108
+2007,BDI,0.025
+2007,KHM,0.253
+2007,CMR,0.406
+2007,CAN,18.068
+2007,CPV,1.002
+2007,CALF,0.053
+2007,TCD,0.102
+2007,CHL,4.272
+2007,CHN,5.285
+2007,CXR,0
+2007,COL,1.397
+2007,COM,0.172
+2007,COG,1.077
+2007,COK,4.246
+2007,CRI,1.793
+2007,CIV,0.336
+2007,HRV,5.643
+2007,CUB,2.325
+2007,CUW,39.314
+2007,CYP,7.919
+2007,CZE,12.419
+2007,COD,0.044
+2007,DNK,9.984
+2007,DJI,0.534
+2007,DMA,2.503
+2007,DOM,2.126
+2007,TLS,0.283
+2007,ECU,2.405
+2007,EGY,2.296
+2007,SLV,1.133
+2007,GNQ,6.033
+2007,ERI,0.193
+2007,EST,14.876
+2007,SWZ,0.96
+2007,ETH,0.071
+2007,FRO,14.237
+2007,FJI,1.28
+2007,FIN,12.623
+2007,FRA,6.46
+2007,PYF,2.924
+2007,GAB,3.251
+2007,GMB,0.197
+2007,GEO,1.628
+2007,DEU,10.473
+2007,GHA,0.379
+2007,GRC,10.327
+2007,GRL,11.525
+2007,GRD,2.131
+2007,GTM,0.887
+2007,GIN,0.206
+2007,GNB,0.159
+2007,GUY,2.354
+2007,HTI,0.188
+2007,HND,1.037
+2007,HKG,6.223
+2007,HUN,5.839
+2007,ISL,11.207
+2007,IND,1.17
+2007,IDN,1.651
+2007,IRN,6.949
+2007,IRQ,2.131
+2007,IRL,10.933
+2007,ISR,9.033
+2007,ITA,8.347
+2007,JAM,3.979
+2007,JPN,10.178
+2007,JOR,3.311
+2007,KAZ,14.166
+2007,KEN,0.258
+2007,KIR,0.467
+2007,KWT,30.78
+2007,KGZ,1.232
+2007,LAO,0.304
+2007,LVA,3.97
+2007,LBN,2.858
+2007,ALSO,1.046
+2007,LBR,0.172
+2007,LBY,8.462
+2007,LIE,5.708
+2007,LTU,4.729
+2007,LUX,23.562
+2007,MAC,2.645
+2007,MDG,0.087
+2007,MWI,0.068
+2007,MYS,6.476
+2007,MDV,2.4
+2007,MLI,0.127
+2007,MLT,6.628
+2007,MHL,2.304
+2007,MRT,0.557
+2007,MUS,2.901
+2007,MEX,4.374
+2007,FSM,1.204
+2007,MDA,1.284
+2007,MCO,0
+2007,MNG,4.589
+2007,MNE,3.253
+2007,MSR,8.409
+2007,MAR,1.566
+2007,MOZ,0.103
+2007,MMR,0.265
+2007,NAME,1.126
+2007,NRU,4.273
+2007,NPL,0.096
+2007,NLD,10.539
+2007,NCL,11.736
+2007,NZL,8.625
+2007,NIC,0.812
+2007,NER,0.048
+2007,NGA,0.553
+2007,NIU,1.985
+2007,PRK,2.606
+2007,MKD,4.326
+2007,NOR,9.68
+2007,OMN,17.296
+2007,PAK,0.854
+2007,PLW,13.043
+2007,PSE,0.625
+2007,PAN,2.142
+2007,PNG,0.951
+2007,PRY,0.723
+2007,PER,1.199
+2007,PHL,0.79
+2007,POL,8.72
+2007,PRT,5.902
+2007,QAT,48.204
+2007,ROU,5.255
+2007,RUS,11.346
+2007,RWA,0.057
+2007,SHN,2.059
+2007,KNA,4.596
+2007,LCA,2.537
+2007,SPM,10.83
+2007,VCT,2.116
+2007,WSM,0.942
+2007,SMR,0
+2007,STP,0.496
+2007,SAU,14.928
+2007,SEN,0.42
+2007,SRB,7.676
+2007,SYC,4.562
+2007,SLE,0.082
+2007,SGP,6.601
+2007,SXM,20.594
+2007,SVK,7.618
+2007,SVN,8.572
+2007,SLB,0.595
+2007,SOME,0.055
+2007,ZAF,9.302
+2007,KOR,10.819
+2007,SSD,0.139
+2007,ESP,8.12
+2007,LKA,0.605
+2007,SDN,0.434
+2007,SURE,3.327
+2007,SWE,5.783
+2007,CHE,5.75
+2007,SYR,3.22
+2007,TWN,12.196
+2007,TJK,0.449
+2007,TZA,0.137
+2007,THA,3.353
+2007,TGO,0.251
+2007,TON,1.065
+2007,TO,32.915
+2007,TUN,2.324
+2007,TUR,4.452
+2007,TKM,9.788
+2007,TCA,9.616
+2007,TUV,1.081
+2007,UGA,0.091
+2007,UKR,7.273
+2007,ARE,22.669
+2007,GBR,9.134
+2007,USA,20.249
+2007,URY,1.787
+2007,UZB,4.534
+2007,VUT,0.433
+2007,VAT,0
+2007,VEN,5.594
+2007,VNM,1.197
+2007,WLF,2.096
+2007,YEM,1.041
+2007,ZMB,0.185
+2007,ZWE,0.79
+2008,AFG,0.161
+2008,,4.704
+2008,ALB,1.49
+2008,DZA,3.213
+2008,AND,7.08
+2008,AGO,0.864
+2008,AIA,9.768
+2008,ATA,0
+2008,ATG,6.468
+2008,ARG,4.649
+2008,ARM,1.869
+2008,ABW,27.104
+2008,AUS,19.026
+2008,AUT,8.832
+2008,AZE,3.671
+2008,BHS,5.282
+2008,BHR,26.731
+2008,BGD,0.312
+2008,BRB,7.634
+2008,BLR,6.42
+2008,BEL,11.203
+2008,BLZ,1.426
+2008,BEN,0.44
+2008,BMU,10.249
+2008,BTN,0.612
+2008,BOL,1.312
+2008,BES,4.096
+2008,BIH,5.061
+2008,BWA,2.243
+2008,BRA,2.142
+2008,VGB,7.429
+2008,BRN,23.833
+2008,BGR,7.039
+2008,BFA,0.114
+2008,BDI,0.025
+2008,KHM,0.277
+2008,CMR,0.39
+2008,CAN,17.364
+2008,CPV,0.919
+2008,CALF,0.037
+2008,TCD,0.079
+2008,CHL,4.234
+2008,CHN,5.64
+2008,CXR,0
+2008,COL,1.528
+2008,COM,0.175
+2008,COG,1.044
+2008,COK,4.099
+2008,CRI,1.775
+2008,CIV,0.332
+2008,HRV,5.37
+2008,CUB,2.493
+2008,CUW,37.212
+2008,CYP,7.978
+2008,CZE,11.847
+2008,COD,0.043
+2008,DNK,9.315
+2008,DJI,0.564
+2008,DMA,2.45
+2008,DOM,2.135
+2008,TLS,0.273
+2008,ECU,2.091
+2008,EGY,2.358
+2008,SLV,1.055
+2008,GNQ,5.972
+2008,ERI,0.136
+2008,EST,13.33
+2008,SWZ,0.938
+2008,ETH,0.076
+2008,FRO,13.011
+2008,FJI,0.943
+2008,FIN,11.033
+2008,FRA,6.309
+2008,PYF,2.978
+2008,GAB,3.362
+2008,GMB,0.199
+2008,GEO,1.368
+2008,DEU,10.501
+2008,GHA,0.342
+2008,GRC,10.031
+2008,GRL,11.968
+2008,GRD,2.247
+2008,GTM,0.779
+2008,GIN,0.204
+2008,GNB,0.153
+2008,GUY,2.264
+2008,HTI,0.184
+2008,HND,1.053
+2008,HKG,6.069
+2008,HUN,5.719
+2008,ISL,12.0
+2008,IND,1.234
+2008,IDN,1.537
+2008,IRN,7.127
+2008,IRQ,3.215
+2008,IRL,10.638
+2008,ISR,9.609
+2008,ITA,8.089
+2008,JAM,3.976
+2008,JPN,9.619
+2008,JOR,3.124
+2008,KAZ,14.005
+2008,KEN,0.261
+2008,KIR,0.458
+2008,KWT,31.793
+2008,KGZ,1.399
+2008,LAO,0.347
+2008,LVA,3.813
+2008,LBN,3.564
+2008,ALSO,1.065
+2008,LBR,0.14
+2008,LBY,8.59
+2008,LIE,6.197
+2008,LTU,4.627
+2008,LUX,22.864
+2008,MAC,2.14
+2008,MDG,0.087
+2008,MWI,0.072
+2008,MYS,6.971
+2008,MDV,2.501
+2008,MLI,0.134
+2008,MLT,6.508
+2008,MHL,2.382
+2008,MRT,0.567
+2008,MUS,2.953
+2008,MEX,4.313
+2008,FSM,1.011
+2008,MDA,1.361
+2008,MCO,0
+2008,MNG,4.525
+2008,MNE,4.125
+2008,MSR,9.849
+2008,MAR,1.624
+2008,MOZ,0.1
+2008,MMR,0.201
+2008,NAME,1.326
+2008,NRU,4.282
+2008,NPL,0.125
+2008,NLD,10.675
+2008,NCL,11.253
+2008,NZL,8.804
+2008,NIC,0.768
+2008,NER,0.051
+2008,NGA,0.573
+2008,NIU,3.968
+2008,PRK,2.874
+2008,MKD,4.284
+2008,NOR,9.368
+2008,OMN,17.797
+2008,PAK,0.828
+2008,PLW,10.731
+2008,PSE,0.539
+2008,PAN,2.098
+2008,PNG,0.777
+2008,PRY,0.758
+2008,PER,1.225
+2008,PHL,0.847
+2008,POL,8.555
+2008,PRT,5.673
+2008,QAT,42.676
+2008,ROU,5.238
+2008,RUS,11.546
+2008,RWA,0.054
+2008,SHN,2.081
+2008,KNA,4.582
+2008,LCA,2.521
+2008,SPM,10.838
+2008,VCT,1.993
+2008,WSM,0.841
+2008,SMR,0
+2008,STP,0.484
+2008,SAU,16.049
+2008,SEN,0.399
+2008,SRB,6.705
+2008,SYC,4.618
+2008,SLE,0.083
+2008,SGP,9.653
+2008,SXM,19.614
+2008,SVK,7.686
+2008,SVN,8.986
+2008,SLB,0.596
+2008,SOME,0.053
+2008,ZAF,9.793
+2008,KOR,11.005
+2008,SSD,0.14
+2008,ESP,7.307
+2008,LKA,0.591
+2008,SDN,0.441
+2008,SURE,3.618
+2008,SWE,5.511
+2008,CHE,5.853
+2008,SYR,3.155
+2008,TWN,11.592
+2008,TJK,0.395
+2008,TZA,0.138
+2008,THA,3.331
+2008,TGO,0.245
+2008,TON,1.131
+2008,TO,31.924
+2008,TUN,2.391
+2008,TUR,4.355
+2008,TKM,11.654
+2008,TCA,9.61
+2008,TUV,1.068
+2008,UGA,0.091
+2008,UKR,7.077
+2008,ARE,21.99
+2008,GBR,8.826
+2008,USA,19.35
+2008,URY,2.458
+2008,UZB,4.63
+2008,VUT,0.407
+2008,VAT,0
+2008,VEN,5.567
+2008,VNM,1.335
+2008,WLF,1.606
+2008,YEM,1.046
+2008,ZMB,0.199
+2008,ZWE,0.615
+2009,AFG,0.233
+2009,,4.565
+2009,ALB,1.504
+2009,DZA,3.377
+2009,AND,6.993
+2009,AGO,0.918
+2009,AIA,9.605
+2009,ATA,0
+2009,ATG,6.587
+2009,ARG,4.378
+2009,ARM,1.472
+2009,ABW,26.477
+2009,AUS,18.812
+2009,AUT,8.069
+2009,AZE,3.191
+2009,BHS,5.853
+2009,BHR,23.823
+2009,BGD,0.335
+2009,BRB,7.305
+2009,BLR,6.208
+2009,BEL,9.977
+2009,BLZ,1.644
+2009,BEN,0.461
+2009,BMU,7.516
+2009,BTN,0.559
+2009,BOL,1.347
+2009,BES,4.039
+2009,BIH,5.296
+2009,BWA,1.882
+2009,BRA,2.004
+2009,VGB,7.174
+2009,BRN,19.842
+2009,BGR,5.994
+2009,BFA,0.117
+2009,BDI,0.019
+2009,KHM,0.322
+2009,CMR,0.443
+2009,CAN,16.198
+2009,CPV,1.009
+2009,CALF,0.035
+2009,TCD,0.106
+2009,CHL,3.918
+2009,CHN,5.893
+2009,CXR,0
+2009,COL,1.635
+2009,COM,0.211
+2009,COG,1.161
+2009,COK,3.959
+2009,CRI,1.698
+2009,CIV,0.274
+2009,HRV,4.976
+2009,CUB,2.517
+2009,CUW,37.489
+2009,CYP,7.624
+2009,CZE,11.035
+2009,COD,0.038
+2009,DNK,8.84
+2009,DJI,0.492
+2009,DMA,2.716
+2009,DOM,2.05
+2009,TLS,0.302
+2009,ECU,2.279
+2009,EGY,2.415
+2009,SLV,1.036
+2009,GNQ,4.696
+2009,ERI,0.161
+2009,EST,10.803
+2009,SWZ,0.967
+2009,ETH,0.074
+2009,FRO,11.872
+2009,FJI,0.823
+2009,FIN,10.472
+2009,FRA,5.974
+2009,PYF,2.972
+2009,GAB,3.208
+2009,GMB,0.199
+2009,GEO,1.592
+2009,DEU,9.701
+2009,GHA,0.275
+2009,GRC,9.436
+2009,GRL,10.494
+2009,GRD,2.232
+2009,GTM,0.802
+2009,GIN,0.211
+2009,GNB,0.154
+2009,GUY,2.541
+2009,HTI,0.194
+2009,HND,0.97
+2009,HKG,5.861
+2009,HUN,5.15
+2009,ISL,11.703
+2009,IND,1.318
+2009,IDN,1.655
+2009,IRN,7.22
+2009,IRQ,3.461
+2009,IRL,9.374
+2009,ISR,8.87
+2009,ITA,7.135
+2009,JAM,2.914
+2009,JPN,9.078
+2009,JOR,3.147
+2009,KAZ,13.69
+2009,KEN,0.306
+2009,KIR,0.449
+2009,KWT,31.735
+2009,KGZ,1.239
+2009,LAO,0.428
+2009,LVA,3.507
+2009,LBN,4.233
+2009,ALSO,1.103
+2009,LBR,0.121
+2009,LBY,8.396
+2009,LIE,5.753
+2009,LTU,4.034
+2009,LUX,21.351
+2009,MAC,3.482
+2009,MDG,0.081
+2009,MWI,0.071
+2009,MYS,6.809
+2009,MDV,2.53
+2009,MLI,0.126
+2009,MLT,6.08
+2009,MHL,2.46
+2009,MRT,0.606
+2009,MUS,2.897
+2009,MEX,4.146
+2009,FSM,1.359
+2009,MDA,1.211
+2009,MCO,0
+2009,MNG,4.885
+2009,MNE,2.664
+2009,MSR,8.98
+2009,MAR,1.587
+2009,MOZ,0.109
+2009,MMR,0.208
+2009,NAME,1.336
+2009,NRU,3.93
+2009,NPL,0.153
+2009,NLD,10.301
+2009,NCL,11.813
+2009,NZL,8.046
+2009,NIC,0.77
+2009,NER,0.059
+2009,NGA,0.493
+2009,NIU,1.99
+2009,PRK,2.175
+2009,MKD,3.941
+2009,NOR,8.921
+2009,OMN,16.405
+2009,PAK,0.817
+2009,PLW,10.497
+2009,PSE,0.536
+2009,PAN,2.377
+2009,PNG,0.715
+2009,PRY,0.795
+2009,PER,1.362
+2009,PHL,0.824
+2009,POL,8.197
+2009,PRT,5.395
+2009,QAT,40.411
+2009,ROU,4.323
+2009,RUS,10.792
+2009,RWA,0.056
+2009,SHN,2.072
+2009,KNA,4.724
+2009,LCA,2.505
+2009,SPM,10.853
+2009,VCT,2.535
+2009,WSM,0.872
+2009,SMR,0
+2009,STP,0.514
+2009,SAU,16.613
+2009,SEN,0.431
+2009,SRB,5.97
+2009,SYC,4.829
+2009,SLE,0.08
+2009,SGP,9.031
+2009,SXM,19.881
+2009,SVK,6.982
+2009,SVN,7.922
+2009,SLB,0.604
+2009,SOME,0.051
+2009,ZAF,9.392
+2009,KOR,11.069
+2009,SSD,0.14
+2009,ESP,6.395
+2009,LKA,0.632
+2009,SDN,0.448
+2009,SURE,3.687
+2009,SWE,5.079
+2009,CHE,5.628
+2009,SYR,2.885
+2009,TWN,10.957
+2009,TJK,0.327
+2009,TZA,0.131
+2009,THA,3.374
+2009,TGO,0.428
+2009,TON,1.231
+2009,TO,31.717
+2009,TUN,2.357
+2009,TUR,4.381
+2009,TKM,10.126
+2009,TCA,9.608
+2009,TUV,1.054
+2009,UGA,0.095
+2009,UKR,6.053
+2009,ARE,21.021
+2009,GBR,7.938
+2009,USA,17.765
+2009,URY,2.376
+2009,UZB,3.878
+2009,VUT,0.504
+2009,VAT,0
+2009,VEN,5.36
+2009,VNM,1.44
+2009,WLF,2.185
+2009,YEM,1.114
+2009,ZMB,0.221
+2009,ZWE,0.651
+2010,AFG,0.297
+2010,,4.768
+2010,ALB,1.642
+2010,DZA,3.301
+2010,AND,7.221
+2010,AGO,0.984
+2010,AIA,9.999
+2010,ATA,0
+2010,ATG,6.412
+2010,ARG,4.522
+2010,ARM,1.443
+2010,ABW,24.973
+2010,AUS,18.416
+2010,AUT,8.612
+2010,AZE,2.982
+2010,BHS,5.968
+2010,BHR,23.957
+2010,BGD,0.364
+2010,BRB,6.716
+2010,BLR,6.417
+2010,BEL,10.535
+2010,BLZ,1.672
+2010,BEN,0.495
+2010,BMU,9.583
+2010,BTN,0.691
+2010,BOL,1.44
+2010,BES,2.684
+2010,BIH,5.548
+2010,BWA,2.169
+2010,BRA,2.242
+2010,VGB,7.706
+2010,BRN,20.243
+2010,BGR,6.297
+2010,BFA,0.126
+2010,BDI,0.033
+2010,KHM,0.354
+2010,CMR,0.422
+2010,CAN,16.372
+2010,CPV,1.069
+2010,CALF,0.036
+2010,TCD,0.104
+2010,CHL,4.196
+2010,CHN,6.394
+2010,CXR,0
+2010,COL,1.702
+2010,COM,0.246
+2010,COG,1.239
+2010,COK,4.251
+2010,CRI,1.621
+2010,CIV,0.295
+2010,HRV,4.811
+2010,CUB,3.029
+2010,CUW,25.439
+2010,CYP,7.171
+2010,CZE,11.227
+2010,COD,0.041
+2010,DNK,8.862
+2010,DJI,0.562
+2010,DMA,2.504
+2010,DOM,2.102
+2010,TLS,0.288
+2010,ECU,2.42
+2010,EGY,2.334
+2010,SLV,1.033
+2010,GNQ,5.849
+2010,ERI,0.158
+2010,EST,14.251
+2010,SWZ,0.906
+2010,ETH,0.071
+2010,FRO,13.012
+2010,FJI,1.206
+2010,FIN,11.948
+2010,FRA,6.03
+2010,PYF,3.254
+2010,GAB,3.411
+2010,GMB,0.219
+2010,GEO,1.618
+2010,DEU,10.22
+2010,GHA,0.35
+2010,GRC,8.823
+2010,GRL,12.052
+2010,GRD,2.281
+2010,GTM,0.763
+2010,GIN,0.242
+2010,GNB,0.152
+2010,GUY,2.523
+2010,HTI,0.217
+2010,HND,0.946
+2010,HKG,5.617
+2010,HUN,5.216
+2010,ISL,11.395
+2010,IND,1.352
+2010,IDN,1.827
+2010,IRN,7.336
+2010,IRQ,3.631
+2010,IRL,9.237
+2010,ISR,9.318
+2010,ITA,7.297
+2010,JAM,2.808
+2010,JPN,9.482
+2010,JOR,2.974
+2010,KAZ,14.963
+2010,KEN,0.293
+2010,KIR,0.543
+2010,KWT,30.873
+2010,KGZ,1.159
+2010,LAO,0.475
+2010,LVA,4.071
+2010,LBN,4.003
+2010,ALSO,1.125
+2010,LBR,0.188
+2010,LBY,9.409
+2010,LIE,5.309
+2010,LTU,4.397
+2010,LUX,22.083
+2010,MAC,2.216
+2010,MDG,0.086
+2010,MWI,0.066
+2010,MYS,6.942
+2010,MDV,2.584
+2010,MLI,0.134
+2010,MLT,6.216
+2010,MHL,2.537
+2010,MRT,0.612
+2010,MUS,3.049
+2010,MEX,4.057
+2010,FSM,0.953
+2010,MDA,1.313
+2010,MCO,0
+2010,MNG,5.096
+2010,MNE,3.838
+2010,MSR,11.819
+2010,MAR,1.675
+2010,MOZ,0.114
+2010,MMR,0.266
+2010,NAME,1.353
+2010,NRU,4.285
+2010,NPL,0.178
+2010,NLD,10.974
+2010,NCL,13.991
+2010,NZL,8.009
+2010,NIC,0.763
+2010,NER,0.07
+2010,NGA,0.692
+2010,NIU,1.993
+2010,PRK,2.029
+2010,MKD,3.909
+2010,NOR,9.33
+2010,OMN,17.748
+2010,PAK,0.791
+2010,PLW,11.251
+2010,PSE,0.509
+2010,PAN,2.504
+2010,PNG,0.622
+2010,PRY,0.871
+2010,PER,1.454
+2010,PHL,0.878
+2010,POL,8.659
+2010,PRT,4.999
+2010,QAT,42.849
+2010,ROU,4.234
+2010,RUS,11.399
+2010,RWA,0.056
+2010,SHN,2.059
+2010,KNA,4.635
+2010,LCA,2.851
+2010,SPM,10.856
+2010,VCT,2.011
+2010,WSM,0.941
+2010,SMR,0
+2010,STP,0.563
+2010,SAU,17.836
+2010,SEN,0.548
+2010,SRB,5.97
+2010,SYC,4.797
+2010,SLE,0.084
+2010,SGP,8.337
+2010,SXM,13.581
+2010,SVK,7.117
+2010,SVN,8.001
+2010,SLB,0.61
+2010,SOME,0.051
+2010,ZAF,8.941
+2010,KOR,12.176
+2010,SSD,0.135
+2010,ESP,6.075
+2010,LKA,0.631
+2010,SDN,0.445
+2010,SURE,4.34
+2010,SWE,5.662
+2010,CHE,5.758
+2010,SYR,2.763
+2010,TWN,11.703
+2010,TJK,0.333
+2010,TZA,0.153
+2010,THA,3.53
+2010,TGO,0.395
+2010,TON,1.092
+2010,TO,33.406
+2010,TUN,2.583
+2010,TUR,4.32
+2010,TKM,11.235
+2010,TCA,9.483
+2010,TUV,1.04
+2010,UGA,0.11
+2010,UKR,6.444
+2010,ARE,21.793
+2010,GBR,8.157
+2010,USA,18.252
+2010,URY,1.879
+2010,UZB,3.763
+2010,VUT,0.493
+2010,VAT,0
+2010,VEN,6.593
+2010,VNM,1.597
+2010,WLF,2.226
+2010,YEM,1.039
+2010,ZMB,0.226
+2010,ZWE,0.682
+2011,AFG,0.405
+2011,,4.869
+2011,ALB,1.832
+2011,DZA,3.423
+2011,AND,6.956
+2011,AGO,0.964
+2011,AIA,9.322
+2011,ATA,0
+2011,ATG,6.293
+2011,ARG,4.568
+2011,ARM,1.696
+2011,ABW,24.667
+2011,AUS,18.082
+2011,AUT,8.331
+2011,AZE,3.228
+2011,BHS,5.594
+2011,BHR,23.486
+2011,BGD,0.377
+2011,BRB,7.047
+2011,BLR,6.316
+2011,BEL,9.59
+2011,BLZ,1.679
+2011,BEN,0.461
+2011,BMU,6.976
+2011,BTN,1.026
+2011,BOL,1.534
+2011,BES,3.318
+2011,BIH,6.348
+2011,BWA,1.888
+2011,BRA,2.334
+2011,VGB,7.594
+2011,BRN,23.798
+2011,BGR,7.043
+2011,BFA,0.128
+2011,BDI,0.036
+2011,KHM,0.362
+2011,CMR,0.396
+2011,CAN,16.468
+2011,CPV,1.167
+2011,CALF,0.039
+2011,TCD,0.103
+2011,CHL,4.546
+2011,CHN,7.024
+2011,CXR,0
+2011,COL,1.682
+2011,COM,0.213
+2011,COG,1.101
+2011,COK,4.53
+2011,CRI,1.562
+2011,CIV,0.303
+2011,HRV,4.745
+2011,CUB,2.605
+2011,CUW,31.993
+2011,CYP,6.801
+2011,CZE,10.975
+2011,COD,0.044
+2011,DNK,7.933
+2011,DJI,0.505
+2011,DMA,2.238
+2011,DOM,2.153
+2011,TLS,0.39
+2011,ECU,2.511
+2011,EGY,2.432
+2011,SLV,1.059
+2011,GNQ,5.325
+2011,ERI,0.18
+2011,EST,14.28
+2011,SWZ,0.912
+2011,ETH,0.081
+2011,FRO,11.731
+2011,FJI,1.099
+2011,FIN,10.512
+2011,FRA,5.641
+2011,PYF,3.108
+2011,GAB,3.288
+2011,GMB,0.222
+2011,GEO,2.042
+2011,DEU,9.919
+2011,GHA,0.394
+2011,GRC,8.588
+2011,GRL,12.894
+2011,GRD,2.2
+2011,GTM,0.758
+2011,GIN,0.246
+2011,GNB,0.15
+2011,GUY,2.634
+2011,HTI,0.228
+2011,HND,1.028
+2011,HKG,6.014
+2011,HUN,5.049
+2011,ISL,10.976
+2011,IND,1.403
+2011,IDN,2.026
+2011,IRN,7.418
+2011,IRQ,3.797
+2011,IRL,8.374
+2011,ISR,9.195
+2011,ITA,7.076
+2011,JAM,3.006
+2011,JPN,9.879
+2011,JOR,2.988
+2011,KAZ,14.171
+2011,KEN,0.315
+2011,KIR,0.5
+2011,KWT,27.965
+2011,KGZ,1.372
+2011,LAO,0.494
+2011,LVA,3.761
+2011,LBN,4.017
+2011,ALSO,1.503
+2011,LBR,0.203
+2011,LBY,9.027
+2011,LIE,4.882
+2011,LTU,4.504
+2011,LUX,21.425
+2011,MAC,2.265
+2011,MDG,0.106
+2011,MWI,0.07
+2011,MYS,6.971
+2011,MDV,2.632
+2011,MLI,0.142
+2011,MLT,6.1
+2011,MHL,2.627
+2011,MRT,0.633
+2011,MUS,3.045
+2011,MEX,4.235
+2011,FSM,1.087
+2011,MDA,1.375
+2011,MCO,0
+2011,MNG,7.787
+2011,MNE,3.81
+2011,MSR,7.344
+2011,MAR,1.668
+2011,MOZ,0.131
+2011,MMR,0.304
+2011,NAME,1.305
+2011,NRU,3.912
+2011,NPL,0.191
+2011,NLD,10.126
+2011,NCL,13.873
+2011,NZL,7.821
+2011,NIC,0.809
+2011,NER,0.076
+2011,NGA,0.758
+2011,NIU,3.998
+2011,PRK,1.46
+2011,MKD,4.214
+2011,NOR,9.033
+2011,OMN,17.769
+2011,PAK,0.779
+2011,PLW,11.839
+2011,PSE,0.549
+2011,PAN,2.698
+2011,PNG,0.679
+2011,PRY,0.89
+2011,PER,1.431
+2011,PHL,0.871
+2011,POL,8.638
+2011,PRT,4.897
+2011,QAT,45.169
+2011,ROU,4.59
+2011,RUS,11.76
+2011,RWA,0.061
+2011,SHN,2.044
+2011,KNA,4.849
+2011,LCA,2.831
+2011,SPM,10.872
+2011,VCT,1.988
+2011,WSM,0.97
+2011,SMR,0
+2011,STP,0.532
+2011,SAU,16.68
+2011,SEN,0.607
+2011,SRB,6.424
+2011,SYC,4.334
+2011,SLE,0.107
+2011,SGP,6.441
+2011,SXM,17.181
+2011,SVK,7.031
+2011,SVN,7.92
+2011,SLB,0.622
+2011,SOME,0.05
+2011,ZAF,8.884
+2011,KOR,12.679
+2011,SSD,0.125
+2011,ESP,6.073
+2011,LKA,0.714
+2011,SDN,0.426
+2011,SURE,4.99
+2011,SWE,5.198
+2011,CHE,5.18
+2011,SYR,2.588
+2011,TWN,11.938
+2011,TJK,0.301
+2011,TZA,0.162
+2011,THA,3.564
+2011,TGO,0.371
+2011,TON,0.953
+2011,TO,33.146
+2011,TUN,2.396
+2011,TUR,4.612
+2011,TKM,12.154
+2011,TCA,9.148
+2011,TUV,1.025
+2011,UGA,0.115
+2011,UKR,6.775
+2011,ARE,23.04
+2011,GBR,7.422
+2011,USA,17.67
+2011,URY,2.276
+2011,UZB,4.018
+2011,VUT,0.539
+2011,VAT,0
+2011,VEN,5.788
+2011,VNM,1.727
+2011,WLF,1.984
+2011,YEM,0.884
+2011,ZMB,0.236
+2011,ZWE,0.796
+2012,AFG,0.329
+2012,,4.878
+2012,ALB,1.677
+2012,DZA,3.641
+2012,AND,6.86
+2012,AGO,1.03
+2012,AIA,8.97
+2012,ATA,0
+2012,ATG,6.351
+2012,ARG,4.56
+2012,ARM,1.972
+2012,ABW,13.203
+2012,AUS,17.888
+2012,AUT,7.982
+2012,AZE,3.455
+2012,BHS,5.37
+2012,BHR,22.139
+2012,BGD,0.399
+2012,BRB,6.529
+2012,BLR,6.452
+2012,BEL,9.283
+2012,BLZ,1.337
+2012,BEN,0.44
+2012,BMU,5.763
+2012,BTN,1.158
+2012,BOL,1.659
+2012,BES,3.925
+2012,BIH,6.024
+2012,BWA,2.334
+2012,BRA,2.492
+2012,VGB,7.472
+2012,BRN,23.362
+2012,BGR,6.449
+2012,BFA,0.153
+2012,BDI,0.036
+2012,KHM,0.377
+2012,CMR,0.362
+2012,CAN,16.309
+2012,CPV,0.94
+2012,CALF,0.04
+2012,TCD,0.137
+2012,CHL,4.591
+2012,CHN,7.156
+2012,CXR,0
+2012,COL,1.753
+2012,COM,0.225
+2012,COG,1.065
+2012,COK,4.674
+2012,CRI,1.533
+2012,CIV,0.388
+2012,HRV,4.405
+2012,CUB,2.638
+2012,CUW,38.347
+2012,CYP,6.28
+2012,CZE,10.589
+2012,COD,0.039
+2012,DNK,7.117
+2012,DJI,0.511
+2012,DMA,2.393
+2012,DOM,2.119
+2012,TLS,0.52
+2012,ECU,2.416
+2012,EGY,2.343
+2012,SLV,1.05
+2012,GNQ,5.781
+2012,ERI,0.187
+2012,EST,13.436
+2012,SWZ,1.052
+2012,ETH,0.086
+2012,FRO,12.186
+2012,FJI,1.063
+2012,FIN,9.447
+2012,FRA,5.651
+2012,PYF,3.056
+2012,GAB,3.065
+2012,GMB,0.217
+2012,GEO,2.17
+2012,DEU,9.968
+2012,GHA,0.471
+2012,GRC,8.336
+2012,GRL,10.313
+2012,GRD,2.339
+2012,GTM,0.765
+2012,GIN,0.224
+2012,GNB,0.149
+2012,GUY,2.635
+2012,HTI,0.224
+2012,HND,1.048
+2012,HKG,5.908
+2012,HUN,4.719
+2012,ISL,10.908
+2012,IND,1.511
+2012,IDN,2.062
+2012,IRN,7.609
+2012,IRQ,3.94
+2012,IRL,8.375
+2012,ISR,9.85
+2012,ITA,6.716
+2012,JAM,2.867
+2012,JPN,10.214
+2012,JOR,3.302
+2012,KAZ,14.404
+2012,KEN,0.286
+2012,KIR,0.46
+2012,KWT,29.978
+2012,KGZ,1.791
+2012,LAO,0.522
+2012,LVA,3.662
+2012,LBN,4.313
+2012,ALSO,1.505
+2012,LBR,0.222
+2012,LBY,9.417
+2012,LIE,5.074
+2012,LTU,4.588
+2012,LUX,20.49
+2012,MAC,1.943
+2012,MDG,0.119
+2012,MWI,0.068
+2012,MYS,7.181
+2012,MDV,2.865
+2012,MLI,0.148
+2012,MLT,6.32
+2012,MHL,2.596
+2012,MRT,0.673
+2012,MUS,3.076
+2012,MEX,4.333
+2012,FSM,1.151
+2012,MDA,1.359
+2012,MCO,0
+2012,MNG,12.534
+2012,MNE,3.496
+2012,MSR,8.062
+2012,MAR,1.708
+2012,MOZ,0.125
+2012,MMR,0.235
+2012,NAME,1.548
+2012,NRU,4.202
+2012,NPL,0.2
+2012,NLD,9.859
+2012,NCL,13.658
+2012,NZL,8.15
+2012,NIC,0.754
+2012,NER,0.103
+2012,NGA,0.641
+2012,NIU,3.998
+2012,PRK,1.509
+2012,MKD,4.042
+2012,NOR,8.812
+2012,OMN,17.738
+2012,PAK,0.766
+2012,PLW,12.435
+2012,PSE,0.526
+2012,PAN,2.601
+2012,PNG,0.631
+2012,PRY,0.873
+2012,PER,1.53
+2012,PHL,0.904
+2012,POL,8.439
+2012,PRT,4.742
+2012,QAT,48.97
+2012,ROU,4.525
+2012,RUS,11.845
+2012,RWA,0.066
+2012,SHN,2.032
+2012,KNA,4.604
+2012,LCA,2.836
+2012,SPM,10.905
+2012,VCT,2.135
+2012,WSM,0.943
+2012,SMR,0
+2012,STP,0.579
+2012,SAU,18.391
+2012,SEN,0.556
+2012,SRB,5.762
+2012,SYC,4.459
+2012,SLE,0.123
+2012,SGP,9.423
+2012,SXM,19.796
+2012,SVK,6.639
+2012,SVN,7.616
+2012,SLB,0.613
+2012,SOME,0.049
+2012,ZAF,8.636
+2012,KOR,12.651
+2012,SSD,0.124
+2012,ESP,5.947
+2012,LKA,0.752
+2012,SDN,0.433
+2012,SURE,4.465
+2012,SWE,4.888
+2012,CHE,5.284
+2012,SYR,2.028
+2012,TWN,11.739
+2012,TJK,0.369
+2012,TZA,0.184
+2012,THA,3.794
+2012,TGO,0.32
+2012,TON,0.988
+2012,TO,32.282
+2012,TUN,2.547
+2012,TUR,4.731
+2012,TKM,12.263
+2012,TCA,8.788
+2012,TUV,1.011
+2012,UGA,0.106
+2012,UKR,6.704
+2012,ARE,23.935
+2012,GBR,7.64
+2012,USA,16.877
+2012,URY,2.549
+2012,UZB,4.029
+2012,VUT,0.456
+2012,VAT,0
+2012,VEN,5.963
+2012,VNM,1.616
+2012,WLF,2.02
+2012,YEM,0.776
+2012,ZMB,0.283
+2012,ZWE,0.848
+2013,AFG,0.293
+2013,,4.859
+2013,ALB,1.831
+2013,DZA,3.705
+2013,AND,6.673
+2013,AGO,0.983
+2013,AIA,8.541
+2013,ATA,0
+2013,ATG,6.292
+2013,ARG,4.471
+2013,ARM,1.908
+2013,ABW,8.368
+2013,AUS,17.276
+2013,AUT,7.993
+2013,AZE,3.49
+2013,BHS,5.263
+2013,BHR,24.695
+2013,BGD,0.401
+2013,BRB,6.581
+2013,BLR,6.617
+2013,BEL,9.251
+2013,BLZ,1.276
+2013,BEN,0.443
+2013,BMU,8.421
+2013,BTN,1.26
+2013,BOL,1.654
+2013,BES,3.826
+2013,BIH,6.026
+2013,BWA,2.554
+2013,BRA,2.639
+2013,VGB,7.409
+2013,BRN,18.523
+2013,BGR,5.731
+2013,BFA,0.162
+2013,BDI,0.035
+2013,KHM,0.376
+2013,CMR,0.363
+2013,CAN,16.243
+2013,CPV,0.916
+2013,CALF,0.024
+2013,TCD,0.157
+2013,CHL,4.668
+2013,CHN,7.235
+2013,CXR,0
+2013,COL,1.905
+2013,COM,0.262
+2013,COG,1.22
+2013,COK,4.697
+2013,CRI,1.593
+2013,CIV,0.431
+2013,HRV,4.278
+2013,CUB,2.484
+2013,CUW,35.681
+2013,CYP,5.641
+2013,CZE,10.151
+2013,COD,0.053
+2013,DNK,7.424
+2013,DJI,0.573
+2013,DMA,2.289
+2013,DOM,2.094
+2013,TLS,0.533
+2013,ECU,2.522
+2013,EGY,2.264
+2013,SLV,0.985
+2013,GNQ,6.589
+2013,ERI,0.173
+2013,EST,14.917
+2013,SWZ,1.252
+2013,ETH,0.101
+2013,FRO,13.387
+2013,FJI,1.211
+2013,FIN,9.508
+2013,FRA,5.66
+2013,PYF,3.053
+2013,GAB,3.116
+2013,GMB,0.2
+2013,GEO,2.116
+2013,DEU,10.208
+2013,GHA,0.458
+2013,GRC,7.487
+2013,GRL,10.032
+2013,GRD,2.6
+2013,GTM,0.831
+2013,GIN,0.191
+2013,GNB,0.145
+2013,GUY,2.549
+2013,HTI,0.264
+2013,HND,1.045
+2013,HKG,6.072
+2013,HUN,4.413
+2013,ISL,10.771
+2013,IND,1.545
+2013,IDN,1.931
+2013,IRN,7.621
+2013,IRQ,3.975
+2013,IRL,8.124
+2013,ISR,8.166
+2013,ITA,6.139
+2013,JAM,3.062
+2013,JPN,10.301
+2013,JOR,3.095
+2013,KAZ,14.711
+2013,KEN,0.3
+2013,KIR,0.453
+2013,KWT,22.96
+2013,KGZ,1.702
+2013,LAO,0.646
+2013,LVA,3.628
+2013,LBN,3.903
+2013,ALSO,1.125
+2013,LBR,0.197
+2013,LBY,7.261
+2013,LIE,5.227
+2013,LTU,4.344
+2013,LUX,19.038
+2013,MAC,1.667
+2013,MDG,0.136
+2013,MWI,0.072
+2013,MYS,8.02
+2013,MDV,2.725
+2013,MLI,0.161
+2013,MLT,5.438
+2013,MHL,2.71
+2013,MRT,0.579
+2013,MUS,3.151
+2013,MEX,4.224
+2013,FSM,1.248
+2013,MDA,1.434
+2013,MCO,0
+2013,MNG,15.281
+2013,MNE,3.393
+2013,MSR,9.46
+2013,MAR,1.675
+2013,MOZ,0.142
+2013,MMR,0.251
+2013,NAME,1.165
+2013,NRU,4.444
+2013,NPL,0.227
+2013,NLD,9.775
+2013,NCL,14.364
+2013,NZL,7.918
+2013,NIC,0.753
+2013,NER,0.111
+2013,NGA,0.666
+2013,NIU,3.97
+2013,PRK,1.08
+2013,MKD,3.563
+2013,NOR,8.762
+2013,OMN,17.096
+2013,PAK,0.738
+2013,PLW,12.741
+2013,PSE,0.569
+2013,PAN,2.751
+2013,PNG,0.658
+2013,PRY,0.861
+2013,PER,1.432
+2013,PHL,0.963
+2013,POL,8.338
+2013,PRT,4.597
+2013,QAT,40.682
+2013,ROU,3.955
+2013,RUS,11.395
+2013,RWA,0.07
+2013,SHN,2.018
+2013,KNA,4.677
+2013,LCA,2.801
+2013,SPM,10.943
+2013,VCT,2.046
+2013,WSM,0.971
+2013,SMR,0
+2013,STP,0.567
+2013,SAU,17.297
+2013,SEN,0.578
+2013,SRB,5.89
+2013,SYC,4.28
+2013,SLE,0.15
+2013,SGP,10.023
+2013,SXM,18.941
+2013,SVK,6.569
+2013,SVN,7.3
+2013,SLB,0.642
+2013,SOME,0.049
+2013,ZAF,8.513
+2013,KOR,12.667
+2013,SSD,0.13
+2013,ESP,5.406
+2013,LKA,0.687
+2013,SDN,0.473
+2013,SURE,5.581
+2013,SWE,4.685
+2013,CHE,5.339
+2013,SYR,1.584
+2013,TWN,11.739
+2013,TJK,0.357
+2013,TZA,0.197
+2013,THA,3.791
+2013,TGO,0.231
+2013,TON,1.06
+2013,TO,31.806
+2013,TUN,2.507
+2013,TUR,4.536
+2013,TKM,11.572
+2013,TCA,8.501
+2013,TUV,1.005
+2013,UGA,0.106
+2013,UKR,6.566
+2013,ARE,24.376
+2013,GBR,7.428
+2013,USA,17.159
+2013,URY,2.216
+2013,UZB,3.747
+2013,VUT,0.417
+2013,VAT,0
+2013,VEN,6.65
+2013,VNM,1.678
+2013,WLF,1.759
+2013,YEM,1.007
+2013,ZMB,0.29
+2013,ZWE,0.861
+2014,AFG,0.28
+2014,,4.833
+2014,ALB,2.08
+2014,DZA,3.903
+2014,AND,6.444
+2014,AGO,0.913
+2014,AIA,8.549
+2014,ATA,0
+2014,ATG,6.363
+2014,ARG,4.399
+2014,ARM,1.939
+2014,ABW,8.417
+2014,AUS,16.747
+2014,AUT,7.509
+2014,AZE,3.526
+2014,BHS,5.489
+2014,BHR,23.567
+2014,BGD,0.423
+2014,BRB,6.09
+2014,BLR,6.566
+2014,BEL,8.681
+2014,BLZ,1.331
+2014,BEN,0.452
+2014,BMU,10.58
+2014,BTN,1.396
+2014,BOL,1.822
+2014,BES,3.729
+2014,BIH,5.408
+2014,BWA,3.031
+2014,BRA,2.742
+2014,VGB,7.454
+2014,BRN,21.194
+2014,BGR,6.126
+2014,BFA,0.16
+2014,BDI,0.033
+2014,KHM,0.449
+2014,CMR,0.393
+2014,CAN,15.997
+2014,CPV,0.892
+2014,CALF,0.026
+2014,TCD,0.159
+2014,CHL,4.387
+2014,CHN,7.218
+2014,CXR,0
+2014,COL,2.106
+2014,COM,0.226
+2014,COG,1.106
+2014,COK,4.518
+2014,CRI,1.599
+2014,CIV,0.431
+2014,HRV,4.127
+2014,CUB,2.436
+2014,CUW,40.448
+2014,CYP,5.906
+2014,CZE,9.913
+2014,COD,0.067
+2014,DNK,6.649
+2014,DJI,0.393
+2014,DMA,2.376
+2014,DOM,2.12
+2014,TLS,0.527
+2014,ECU,2.74
+2014,EGY,2.374
+2014,SLV,0.991
+2014,GNQ,5.924
+2014,ERI,0.171
+2014,EST,14.323
+2014,SWZ,0.683
+2014,ETH,0.12
+2014,FRO,12.316
+2014,FJI,1.367
+2014,FIN,8.719
+2014,FRA,5.143
+2014,PYF,2.932
+2014,GAB,3.238
+2014,GMB,0.231
+2014,GEO,2.368
+2014,DEU,9.709
+2014,GHA,0.47
+2014,GRC,7.239
+2014,GRL,9.337
+2014,GRD,1.987
+2014,GTM,0.88
+2014,GIN,0.191
+2014,GNB,0.149
+2014,GUY,2.625
+2014,HTI,0.26
+2014,HND,1.039
+2014,HKG,6.188
+2014,HUN,4.438
+2014,ISL,10.52
+2014,IND,1.643
+2014,IDN,1.904
+2014,IRN,7.912
+2014,IRQ,3.729
+2014,IRL,7.973
+2014,ISR,7.666
+2014,ITA,5.804
+2014,JAM,2.764
+2014,JPN,9.916
+2014,JOR,2.991
+2014,KAZ,15.591
+2014,KEN,0.317
+2014,KIR,0.51
+2014,KWT,20.104
+2014,KGZ,1.761
+2014,LAO,0.665
+2014,LVA,3.567
+2014,LBN,3.824
+2014,ALSO,1.18
+2014,LBR,0.182
+2014,LBY,9.938
+2014,LIE,4.344
+2014,LTU,4.292
+2014,LUX,17.666
+2014,MAC,2.141
+2014,MDG,0.13
+2014,MWI,0.063
+2014,MYS,8.006
+2014,MDV,3.156
+2014,MLI,0.179
+2014,MLT,5.296
+2014,MHL,2.833
+2014,MRT,0.668
+2014,MUS,3.255
+2014,MEX,4.077
+2014,FSM,1.243
+2014,MDA,1.426
+2014,MCO,0
+2014,MNG,10.206
+2014,MNE,3.321
+2014,MSR,9.386
+2014,MAR,1.669
+2014,MOZ,0.312
+2014,MMR,0.31
+2014,NAME,1.64
+2014,NRU,4.678
+2014,NPL,0.276
+2014,NLD,9.288
+2014,NCL,17.838
+2014,NZL,7.85
+2014,NIC,0.764
+2014,NER,0.116
+2014,NGA,0.686
+2014,NIU,5.916
+2014,PRK,1.216
+2014,MKD,3.422
+2014,NOR,8.75
+2014,OMN,16.436
+2014,PAK,0.752
+2014,PLW,12.336
+2014,PSE,0.648
+2014,PAN,2.791
+2014,PNG,0.787
+2014,PRY,0.896
+2014,PER,1.627
+2014,PHL,1.002
+2014,POL,8.024
+2014,PRT,4.601
+2014,QAT,41.174
+2014,ROU,3.957
+2014,RUS,11.366
+2014,RWA,0.073
+2014,SHN,2.006
+2014,KNA,4.828
+2014,LCA,2.767
+2014,SPM,10.968
+2014,VCT,2.364
+2014,WSM,0.999
+2014,SMR,0
+2014,STP,0.649
+2014,SAU,19.076
+2014,SEN,0.624
+2014,SRB,4.932
+2014,SYC,4.673
+2014,SLE,0.158
+2014,SGP,9.355
+2014,SXM,18.543
+2014,SVK,6.211
+2014,SVN,6.536
+2014,SLB,0.552
+2014,SOME,0.047
+2014,ZAF,8.622
+2014,KOR,12.448
+2014,SSD,0.136
+2014,ESP,5.47
+2014,LKA,0.82
+2014,SDN,0.474
+2014,SURE,5.618
+2014,SWE,4.46
+2014,CHE,4.791
+2014,SYR,1.542
+2014,TWN,11.799
+2014,TJK,0.552
+2014,TZA,0.181
+2014,THA,3.895
+2014,TGO,0.212
+2014,TON,1.065
+2014,TO,32.324
+2014,TUN,2.598
+2014,TUR,4.66
+2014,TKM,11.089
+2014,TCA,8.79
+2014,TUV,1.006
+2014,UGA,0.113
+2014,UKR,5.707
+2014,ARE,23.765
+2014,GBR,6.774
+2014,USA,17.168
+2014,URY,1.971
+2014,UZB,3.527
+2014,VUT,0.584
+2014,VAT,0
+2014,VEN,5.828
+2014,VNM,1.993
+2014,WLF,1.782
+2014,YEM,0.954
+2014,ZMB,0.31
+2014,ZWE,0.862
+2015,AFG,0.29
+2015,,4.775
+2015,ALB,1.635
+2015,DZA,4.048
+2015,AND,6.484
+2015,AGO,0.925
+2015,AIA,9.567
+2015,ATA,0
+2015,ATG,6.476
+2015,ARG,4.433
+2015,ARM,1.9
+2015,ABW,8.609
+2015,AUS,16.85
+2015,AUT,7.679
+2015,AZE,3.516
+2015,BHS,5.719
+2015,BHR,23.83
+2015,BGD,0.464
+2015,BRB,6.064
+2015,BLR,6.061
+2015,BEL,8.992
+2015,BLZ,1.782
+2015,BEN,0.487
+2015,BMU,8.527
+2015,BTN,1.421
+2015,BOL,1.793
+2015,BES,4.107
+2015,BIH,5.238
+2015,BWA,2.352
+2015,BRA,2.58
+2015,VGB,7.728
+2015,BRN,16.451
+2015,BGR,6.584
+2015,BFA,0.198
+2015,BDI,0.033
+2015,KHM,0.543
+2015,CMR,0.428
+2015,CAN,15.971
+2015,CPV,0.889
+2015,CALF,0.037
+2015,TCD,0.166
+2015,CHL,4.576
+2015,CHN,7.08
+2015,CXR,0
+2015,COL,2.054
+2015,COM,0.246
+2015,COG,1.112
+2015,COK,4.549
+2015,CRI,1.513
+2015,CIV,0.405
+2015,HRV,4.189
+2015,CUB,2.599
+2015,CUW,43.211
+2015,CYP,5.872
+2015,CZE,9.979
+2015,COD,0.042
+2015,DNK,6.184
+2015,DJI,0.429
+2015,DMA,2.511
+2015,DOM,2.258
+2015,TLS,0.489
+2015,ECU,2.549
+2015,EGY,2.297
+2015,SLV,1.07
+2015,GNQ,5.222
+2015,ERI,0.166
+2015,EST,12.037
+2015,SWZ,0.766
+2015,ETH,0.124
+2015,FRO,12.451
+2015,FJI,1.439
+2015,FIN,8.064
+2015,FRA,5.194
+2015,PYF,2.901
+2015,GAB,3.192
+2015,GMB,0.259
+2015,GEO,2.587
+2015,DEU,9.724
+2015,GHA,0.505
+2015,GRC,6.933
+2015,GRL,9.402
+2015,GRD,2.186
+2015,GTM,0.997
+2015,GIN,0.211
+2015,GNB,0.156
+2015,GUY,2.653
+2015,HTI,0.252
+2015,HND,1.113
+2015,HKG,5.73
+2015,HUN,4.746
+2015,ISL,10.704
+2015,IND,1.689
+2015,IDN,2.081
+2015,IRN,7.722
+2015,IRQ,3.76
+2015,IRL,8.298
+2015,ISR,7.86
+2015,ITA,6.009
+2015,JAM,2.862
+2015,JPN,9.612
+2015,JOR,2.691
+2015,KAZ,15.624
+2015,KEN,0.363
+2015,KIR,0.471
+2015,KWT,23.962
+2015,KGZ,1.735
+2015,LAO,1.36
+2015,LVA,3.646
+2015,LBN,4.022
+2015,ALSO,1.058
+2015,LBR,0.101
+2015,LBY,8.81
+2015,LIE,4.275
+2015,LTU,4.405
+2015,LUX,16.422
+2015,MAC,2.876
+2015,MDG,0.13
+2015,MWI,0.064
+2015,MYS,7.58
+2015,MDV,2.936
+2015,MLI,0.181
+2015,MLT,3.647
+2015,MHL,2.891
+2015,MRT,0.761
+2015,MUS,3.256
+2015,MEX,3.991
+2015,FSM,1.305
+2015,MDA,1.459
+2015,MCO,0
+2015,MNG,7.853
+2015,MNE,3.531
+2015,MSR,10.813
+2015,MAR,1.687
+2015,MOZ,0.251
+2015,MMR,0.425
+2015,NAME,1.687
+2015,NRU,4.904
+2015,NPL,0.25
+2015,NLD,9.612
+2015,NCL,17.23
+2015,NZL,7.801
+2015,NIC,0.862
+2015,NER,0.11
+2015,NGA,0.594
+2015,NIU,3.917
+2015,PRK,0.975
+2015,MKD,3.205
+2015,NOR,8.765
+2015,OMN,16.073
+2015,PAK,0.789
+2015,PLW,11.513
+2015,PSE,0.67
+2015,PAN,2.726
+2015,PNG,0.765
+2015,PRY,0.979
+2015,PER,1.603
+2015,PHL,1.091
+2015,POL,8.111
+2015,PRT,5.036
+2015,QAT,37.768
+2015,ROU,3.92
+2015,RUS,11.327
+2015,RWA,0.083
+2015,SHN,1.991
+2015,KNA,4.904
+2015,LCA,2.733
+2015,SPM,10.99
+2015,VCT,2.202
+2015,WSM,1.116
+2015,SMR,0
+2015,STP,0.674
+2015,SAU,20.728
+2015,SEN,0.686
+2015,SRB,5.821
+2015,SYC,5.02
+2015,SLE,0.148
+2015,SGP,10.96
+2015,SXM,18.489
+2015,SVK,6.355
+2015,SVN,6.558
+2015,SLB,0.478
+2015,SOME,0.046
+2015,ZAF,7.986
+2015,KOR,12.436
+2015,SSD,0.171
+2015,ESP,5.832
+2015,LKA,0.923
+2015,SDN,0.555
+2015,SURE,4.715
+2015,SWE,4.408
+2015,CHE,4.676
+2015,SYR,1.489
+2015,TWN,11.733
+2015,TJK,0.622
+2015,TZA,0.185
+2015,THA,3.942
+2015,TGO,0.249
+2015,TON,1.105
+2015,TO,31.201
+2015,TUN,2.718
+2015,TUR,4.833
+2015,TKM,11.155
+2015,TCA,8.52
+2015,TUV,1.009
+2015,UGA,0.121
+2015,UKR,4.975
+2015,ARE,25.31
+2015,GBR,6.477
+2015,USA,16.563
+2015,URY,1.981
+2015,UZB,3.35
+2015,VUT,0.49
+2015,VAT,0
+2015,VEN,5.433
+2015,VNM,2.337
+2015,WLF,1.801
+2015,YEM,0.494
+2015,ZMB,0.312
+2015,ZWE,0.866
+2016,AFG,0.262
+2016,,4.72
+2016,ALB,1.608
+2016,DZA,3.929
+2016,AND,6.463
+2016,AGO,0.823
+2016,AIA,9.381
+2016,ATA,0
+2016,ATG,6.594
+2016,ARG,4.349
+2016,ARM,1.816
+2016,ABW,8.419
+2016,AUS,16.956
+2016,AUT,7.695
+2016,AZE,3.481
+2016,BHS,5.579
+2016,BHR,22.388
+2016,BGD,0.477
+2016,BRB,6.078
+2016,BLR,5.988
+2016,BEL,8.803
+2016,BLZ,1.706
+2016,BEN,0.577
+2016,BMU,9.815
+2016,BTN,1.682
+2016,BOL,1.886
+2016,BES,4.312
+2016,BIH,6.243
+2016,BWA,2.699
+2016,BRA,2.382
+2016,VGB,7.756
+2016,BRN,17.918
+2016,BGR,6.257
+2016,BFA,0.203
+2016,BDI,0.039
+2016,KHM,0.699
+2016,CMR,0.42
+2016,CAN,15.443
+2016,CPV,0.886
+2016,CALF,0.04
+2016,TCD,0.163
+2016,CHL,4.659
+2016,CHN,6.966
+2016,CXR,0
+2016,COL,2.093
+2016,COM,0.29
+2016,COG,1.105
+2016,COK,4.17
+2016,CRI,1.589
+2016,CIV,0.495
+2016,HRV,4.287
+2016,CUB,2.484
+2016,CUW,36.264
+2016,CYP,6.156
+2016,CZE,10.133
+2016,COD,0.031
+2016,DNK,6.469
+2016,DJI,0.371
+2016,DMA,2.457
+2016,DOM,2.341
+2016,TLS,0.525
+2016,ECU,2.394
+2016,EGY,2.399
+2016,SLV,1.055
+2016,GNQ,5.403
+2016,ERI,0.166
+2016,EST,13.29
+2016,SWZ,0.924
+2016,ETH,0.137
+2016,FRO,12.726
+2016,FJI,1.372
+2016,FIN,8.598
+2016,FRA,5.22
+2016,PYF,2.958
+2016,GAB,3.126
+2016,GMB,0.255
+2016,GEO,2.67
+2016,DEU,9.738
+2016,GHA,0.471
+2016,GRC,6.638
+2016,GRL,9.413
+2016,GRD,2.229
+2016,GTM,1.039
+2016,GIN,0.23
+2016,GNB,0.166
+2016,GUY,3.128
+2016,HTI,0.278
+2016,HND,1.028
+2016,HKG,5.832
+2016,HUN,4.807
+2016,ISL,10.413
+2016,IND,1.759
+2016,IDN,2.063
+2016,IRN,7.608
+2016,IRQ,4.106
+2016,IRL,8.561
+2016,ISR,7.496
+2016,ITA,5.968
+2016,JAM,2.913
+2016,JPN,9.469
+2016,JOR,2.479
+2016,KAZ,15.361
+2016,KEN,0.379
+2016,KIR,0.464
+2016,KWT,26.957
+2016,KGZ,1.599
+2016,LAO,2.388
+2016,LVA,3.654
+2016,LBN,4.225
+2016,ALSO,1.032
+2016,LBR,0.106
+2016,LBY,8.222
+2016,LIE,3.982
+2016,LTU,4.473
+2016,LUX,15.589
+2016,MAC,2.841
+2016,MDG,0.128
+2016,MWI,0.069
+2016,MYS,7.512
+2016,MDV,3.162
+2016,MLI,0.226
+2016,MLT,2.9
+2016,MHL,2.955
+2016,MRT,0.648
+2016,MUS,3.358
+2016,MEX,3.948
+2016,FSM,1.3
+2016,MDA,1.52
+2016,MCO,0
+2016,MNG,8.46
+2016,MNE,3.182
+2016,MSR,5.738
+2016,MAR,1.665
+2016,MOZ,0.309
+2016,MMR,0.407
+2016,NAME,1.744
+2016,NRU,4.475
+2016,NPL,0.362
+2016,NLD,9.617
+2016,NCL,19.319
+2016,NZL,7.316
+2016,NIC,0.846
+2016,NER,0.103
+2016,NGA,0.62
+2016,NIU,3.892
+2016,PRK,1.086
+2016,MKD,3.153
+2016,NOR,8.532
+2016,OMN,14.947
+2016,PAK,0.917
+2016,PLW,11.912
+2016,PSE,0.704
+2016,PAN,2.593
+2016,PNG,0.791
+2016,PRY,1.148
+2016,PER,1.694
+2016,PHL,1.161
+2016,POL,8.397
+2016,PRT,4.874
+2016,QAT,33.672
+2016,ROU,3.875
+2016,RUS,11.267
+2016,RWA,0.088
+2016,SHN,2.649
+2016,KNA,4.981
+2016,LCA,2.7
+2016,SPM,11.01
+2016,VCT,2.282
+2016,WSM,1.159
+2016,SMR,0
+2016,STP,0.734
+2016,SAU,20.871
+2016,SEN,0.679
+2016,SRB,6.011
+2016,SYC,5.504
+2016,SLE,0.152
+2016,SGP,7.054
+2016,SXM,17.981
+2016,SVK,6.428
+2016,SVN,6.918
+2016,SLB,0.461
+2016,SOME,0.045
+2016,ZAF,8.099
+2016,KOR,12.433
+2016,SSD,0.154
+2016,ESP,5.587
+2016,LKA,1.073
+2016,SDN,0.509
+2016,SURE,5.023
+2016,SWE,4.362
+2016,CHE,4.679
+2016,SYR,1.42
+2016,TWN,11.856
+2016,TJK,0.652
+2016,TZA,0.188
+2016,THA,4.023
+2016,TGO,0.302
+2016,TON,1.178
+2016,TO,27.15
+2016,TUN,2.618
+2016,TUR,5.011
+2016,TKM,10.982
+2016,TCA,8.617
+2016,TUV,1.011
+2016,UGA,0.124
+2016,UKR,5.218
+2016,ARE,25.194
+2016,GBR,6.084
+2016,USA,16.054
+2016,URY,1.91
+2016,UZB,3.502
+2016,VUT,0.53
+2016,VAT,0
+2016,VEN,4.957
+2016,VNM,2.397
+2016,WLF,2.122
+2016,YEM,0.342
+2016,ZMB,0.35
+2016,ZWE,0.729
+2017,AFG,0.277
+2017,,4.74
+2017,ALB,1.838
+2017,DZA,4.013
+2017,AND,6.301
+2017,AGO,0.749
+2017,AIA,8.527
+2017,ATA,0
+2017,ATG,6.634
+2017,ARG,4.242
+2017,ARM,1.943
+2017,ABW,8.443
+2017,AUS,16.822
+2017,AUT,7.912
+2017,AZE,3.436
+2017,BHS,6.29
+2017,BHR,22.539
+2017,BGD,0.499
+2017,BRB,5.89
+2017,BLR,6.117
+2017,BEL,8.701
+2017,BLZ,1.643
+2017,BEN,0.587
+2017,BMU,10.015
+2017,BTN,1.774
+2017,BOL,1.939
+2017,BES,4.206
+2017,BIH,6.441
+2017,BWA,2.947
+2017,BRA,2.384
+2017,VGB,5.968
+2017,BRN,21.635
+2017,BGR,6.604
+2017,BFA,0.228
+2017,BDI,0.046
+2017,KHM,0.788
+2017,CMR,0.393
+2017,CAN,15.502
+2017,CPV,0.901
+2017,CALF,0.043
+2017,TCD,0.155
+2017,CHL,4.581
+2017,CHN,7.099
+2017,CXR,0
+2017,COL,1.908
+2017,COM,0.351
+2017,COG,1.064
+2017,COK,4.628
+2017,CRI,1.608
+2017,CIV,0.483
+2017,HRV,4.471
+2017,CUB,2.201
+2017,CUW,28.942
+2017,CYP,6.209
+2017,CZE,10.234
+2017,COD,0.037
+2017,DNK,6.042
+2017,DJI,0.377
+2017,DMA,2.185
+2017,DOM,2.256
+2017,TLS,0.555
+2017,ECU,2.346
+2017,EGY,2.555
+2017,SLV,0.953
+2017,GNQ,5.42
+2017,ERI,0.164
+2017,EST,14.222
+2017,SWZ,0.856
+2017,ETH,0.144
+2017,FRO,14.07
+2017,FJI,1.482
+2017,FIN,8.107
+2017,FRA,5.252
+2017,PYF,2.914
+2017,GAB,2.747
+2017,GMB,0.249
+2017,GEO,2.697
+2017,DEU,9.513
+2017,GHA,0.412
+2017,GRC,7.0
+2017,GRL,9.72
+2017,GRD,2.303
+2017,GTM,1.045
+2017,GIN,0.265
+2017,GNB,0.162
+2017,GUY,3.049
+2017,HTI,0.289
+2017,HND,1.063
+2017,HKG,5.687
+2017,HUN,5.065
+2017,ISL,10.512
+2017,IND,1.792
+2017,IDN,2.106
+2017,IRN,8.111
+2017,IRQ,4.466
+2017,IRL,8.189
+2017,ISR,7.081
+2017,ITA,5.89
+2017,JAM,2.777
+2017,JPN,9.37
+2017,JOR,2.527
+2017,KAZ,16.024
+2017,KEN,0.362
+2017,KIR,0.548
+2017,KWT,25.046
+2017,KGZ,1.529
+2017,LAO,2.813
+2017,LVA,3.691
+2017,LBN,4.554
+2017,ALSO,1.158
+2017,LBR,0.186
+2017,LBY,8.378
+2017,LIE,4.108
+2017,LTU,4.574
+2017,LUX,15.543
+2017,MAC,3.018
+2017,MDG,0.157
+2017,MWI,0.069
+2017,MYS,7.634
+2017,MDV,3.203
+2017,MLI,0.241
+2017,MLT,3.193
+2017,MHL,3.105
+2017,MRT,0.822
+2017,MUS,3.503
+2017,MEX,3.79
+2017,FSM,1.294
+2017,MDA,1.641
+2017,MCO,0
+2017,MNG,11.026
+2017,MNE,3.325
+2017,MSR,6.206
+2017,MAR,1.72
+2017,MOZ,0.242
+2017,MMR,0.452
+2017,NAME,1.783
+2017,NRU,4.694
+2017,NPL,0.441
+2017,NLD,9.426
+2017,NCL,19.636
+2017,NZL,7.519
+2017,NIC,0.843
+2017,NER,0.103
+2017,NGA,0.583
+2017,NIU,3.875
+2017,PRK,2.028
+2017,MKD,3.386
+2017,NOR,8.369
+2017,OMN,15.243
+2017,PAK,0.999
+2017,PLW,12.106
+2017,PSE,0.695
+2017,PAN,2.74
+2017,PNG,0.731
+2017,PRY,1.248
+2017,PER,1.736
+2017,PHL,1.266
+2017,POL,8.74
+2017,PRT,5.346
+2017,QAT,36.917
+2017,ROU,4.043
+2017,RUS,11.455
+2017,RWA,0.094
+2017,SHN,1.991
+2017,KNA,5.058
+2017,LCA,2.771
+2017,SPM,11.029
+2017,VCT,2.048
+2017,WSM,1.182
+2017,SMR,0
+2017,STP,0.669
+2017,SAU,19.912
+2017,SEN,0.71
+2017,SRB,6.099
+2017,SYC,5.649
+2017,SLE,0.142
+2017,SGP,6.942
+2017,SXM,16.877
+2017,SVK,6.639
+2017,SVN,6.966
+2017,SLB,0.444
+2017,SOME,0.043
+2017,ZAF,7.757
+2017,KOR,12.707
+2017,SSD,0.136
+2017,ESP,5.873
+2017,LKA,1.064
+2017,SDN,0.504
+2017,SURE,4.13
+2017,SWE,4.221
+2017,CHE,4.517
+2017,SYR,1.498
+2017,TWN,12.054
+2017,TJK,0.768
+2017,TZA,0.2
+2017,THA,3.997
+2017,TGO,0.254
+2017,TON,1.286
+2017,TO,27.267
+2017,TUN,2.648
+2017,TUR,5.249
+2017,TKM,10.783
+2017,TCA,8.823
+2017,TUV,1.013
+2017,UGA,0.134
+2017,UKR,4.995
+2017,ARE,23.432
+2017,GBR,5.863
+2017,USA,15.804
+2017,URY,1.801
+2017,UZB,3.41
+2017,VUT,0.492
+2017,VAT,0
+2017,VEN,4.523
+2017,VNM,2.442
+2017,WLF,2.144
+2017,YEM,0.323
+2017,ZMB,0.396
+2017,ZWE,0.63
+2018,AFG,0.295
+2018,,4.785
+2018,ALB,1.701
+2018,DZA,4.085
+2018,AND,6.592
+2018,AGO,0.685
+2018,AIA,8.677
+2018,ATA,0
+2018,ATG,6.717
+2018,ARG,4.066
+2018,ARM,2.054
+2018,ABW,8.228
+2018,AUS,16.628
+2018,AUT,7.53
+2018,AZE,3.406
+2018,BHS,6.026
+2018,BHR,21.943
+2018,BGD,0.504
+2018,BRB,5.919
+2018,BLR,6.411
+2018,BEL,8.732
+2018,BLZ,1.582
+2018,BEN,0.623
+2018,BMU,10.947
+2018,BTN,1.939
+2018,BOL,1.939
+2018,BES,3.958
+2018,BIH,6.492
+2018,BWA,3.061
+2018,BRA,2.274
+2018,VGB,5.31
+2018,BRN,21.517
+2018,BGR,6.109
+2018,BFA,0.246
+2018,BDI,0.056
+2018,KHM,0.869
+2018,CMR,0.385
+2018,CAN,15.582
+2018,CPV,0.943
+2018,CALF,0.043
+2018,TCD,0.153
+2018,CHL,4.515
+2018,CHN,7.307
+2018,CXR,0
+2018,COL,1.768
+2018,COM,0.378
+2018,COG,1.203
+2018,COK,4.666
+2018,CRI,1.59
+2018,CIV,0.406
+2018,HRV,4.26
+2018,CUB,2.042
+2018,CUW,23.345
+2018,CYP,6.007
+2018,CZE,10.096
+2018,COD,0.039
+2018,DNK,6.002
+2018,DJI,0.411
+2018,DMA,2.276
+2018,DOM,2.392
+2018,TLS,0.489
+2018,ECU,2.258
+2018,EGY,2.351
+2018,SLV,0.998
+2018,GNQ,4.0
+2018,ERI,0.181
+2018,EST,13.527
+2018,SWZ,0.89
+2018,ETH,0.144
+2018,FRO,14.231
+2018,FJI,1.534
+2018,FIN,8.291
+2018,FRA,5.011
+2018,PYF,2.942
+2018,GAB,2.497
+2018,GMB,0.265
+2018,GEO,2.667
+2018,DEU,9.105
+2018,GHA,0.492
+2018,GRC,6.751
+2018,GRL,9.734
+2018,GRD,2.526
+2018,GTM,1.098
+2018,GIN,0.252
+2018,GNB,0.162
+2018,GUY,3.186
+2018,HTI,0.31
+2018,HND,1.007
+2018,HKG,5.694
+2018,HUN,5.067
+2018,ISL,10.395
+2018,IND,1.894
+2018,IDN,2.225
+2018,IRN,8.295
+2018,IRQ,4.527
+2018,IRL,8.07
+2018,ISR,7.093
+2018,ITA,5.842
+2018,JAM,2.876
+2018,JPN,9.043
+2018,JOR,2.404
+2018,KAZ,16.57
+2018,KEN,0.377
+2018,KIR,0.539
+2018,KWT,24.448
+2018,KGZ,1.795
+2018,LAO,2.893
+2018,LVA,4.062
+2018,LBN,4.396
+2018,ALSO,1.079
+2018,LBR,0.086
+2018,LBY,8.339
+2018,LIE,3.742
+2018,LTU,4.671
+2018,LUX,15.749
+2018,MAC,1.908
+2018,MDG,0.141
+2018,MWI,0.094
+2018,MYS,8.093
+2018,MDV,3.553
+2018,MLI,0.246
+2018,MLT,3.146
+2018,MHL,3.185
+2018,MRT,0.858
+2018,MUS,3.442
+2018,MEX,3.792
+2018,FSM,1.288
+2018,MDA,1.733
+2018,MCO,0
+2018,MNG,14.322
+2018,MNE,3.801
+2018,MSR,6.691
+2018,MAR,1.734
+2018,MOZ,0.238
+2018,MMR,0.646
+2018,NAME,1.727
+2018,NRU,4.6
+2018,NPL,0.519
+2018,NLD,9.148
+2018,NCL,20.756
+2018,NZL,7.379
+2018,NIC,0.791
+2018,NER,0.08
+2018,NGA,0.532
+2018,NIU,5.764
+2018,PRK,1.858
+2018,MKD,3.157
+2018,NOR,8.359
+2018,OMN,14.374
+2018,PAK,0.933
+2018,PLW,11.881
+2018,PSE,0.616
+2018,PAN,2.582
+2018,PNG,0.815
+2018,PRY,1.304
+2018,PER,1.734
+2018,PHL,1.303
+2018,POL,8.725
+2018,PRT,4.993
+2018,QAT,34.504
+2018,ROU,4.094
+2018,RUS,11.757
+2018,RWA,0.103
+2018,SHN,2.001
+2018,KNA,5.137
+2018,LCA,2.863
+2018,SPM,9.821
+2018,VCT,2.297
+2018,WSM,1.136
+2018,SMR,0
+2018,STP,0.659
+2018,SAU,19.615
+2018,SEN,0.727
+2018,SRB,5.962
+2018,SYC,5.827
+2018,SLE,0.131
+2018,SGP,8.605
+2018,SXM,16.297
+2018,SVK,6.629
+2018,SVN,6.895
+2018,SLB,0.439
+2018,SOME,0.041
+2018,ZAF,7.591
+2018,KOR,12.968
+2018,SSD,0.167
+2018,ESP,5.74
+2018,LKA,0.931
+2018,SDN,0.518
+2018,SURE,3.519
+2018,SWE,4.137
+2018,CHE,4.33
+2018,SYR,1.677
+2018,TWN,11.945
+2018,TJK,0.883
+2018,TZA,0.204
+2018,THA,4.054
+2018,TGO,0.269
+2018,TON,1.289
+2018,TO,26.801
+2018,TUN,2.609
+2018,TUR,5.097
+2018,TKM,10.513
+2018,TCA,8.737
+2018,TUV,1.01
+2018,UGA,0.133
+2018,UKR,5.212
+2018,ARE,22.897
+2018,GBR,5.716
+2018,USA,16.191
+2018,URY,1.917
+2018,UZB,3.152
+2018,VUT,0.604
+2018,VAT,0
+2018,VEN,3.377
+2018,VNM,2.715
+2018,WLF,2.166
+2018,YEM,0.376
+2018,ZMB,0.416
+2018,ZWE,0.712
+2019,AFG,0.293
+2019,,4.77
+2019,ALB,1.68
+2019,DZA,4.182
+2019,AND,6.334
+2019,AGO,0.594
+2019,AIA,9.512
+2019,ATA,0
+2019,ATG,6.96
+2019,ARG,3.989
+2019,ARM,2.228
+2019,ABW,8.363
+2019,AUS,16.398
+2019,AUT,7.653
+2019,AZE,3.688
+2019,BHS,6.068
+2019,BHR,25.153
+2019,BGD,0.609
+2019,BRB,5.948
+2019,BLR,6.419
+2019,BEL,8.642
+2019,BLZ,1.817
+2019,BEN,0.579
+2019,BMU,9.299
+2019,BTN,1.901
+2019,BOL,1.919
+2019,BES,4.294
+2019,BIH,6.192
+2019,BWA,2.713
+2019,BRA,2.236
+2019,VGB,5.383
+2019,BRN,24.501
+2019,BGR,5.988
+2019,BFA,0.263
+2019,BDI,0.058
+2019,KHM,1.124
+2019,CMR,0.372
+2019,CAN,15.42
+2019,CPV,0.972
+2019,CALF,0.044
+2019,TCD,0.143
+2019,CHL,4.831
+2019,CHN,7.54
+2019,CXR,0
+2019,COL,1.883
+2019,COM,0.394
+2019,COG,1.309
+2019,COK,4.919
+2019,CRI,1.514
+2019,CIV,0.403
+2019,HRV,4.324
+2019,CUB,1.999
+2019,CUW,11.611
+2019,CYP,5.975
+2019,CZE,9.588
+2019,COD,0.04
+2019,DNK,5.34
+2019,DJI,0.418
+2019,DMA,2.41
+2019,DOM,2.608
+2019,TLS,0.527
+2019,ECU,2.31
+2019,EGY,2.425
+2019,SLV,1.202
+2019,GNQ,2.986
+2019,ERI,0.185
+2019,EST,9.287
+2019,SWZ,0.959
+2019,ETH,0.146
+2019,FRO,14.314
+2019,FJI,1.533
+2019,FIN,7.689
+2019,FRA,4.898
+2019,PYF,2.922
+2019,GAB,2.419
+2019,GMB,0.283
+2019,GEO,2.887
+2019,DEU,8.509
+2019,GHA,0.521
+2019,GRC,6.219
+2019,GRL,9.926
+2019,GRD,2.687
+2019,GTM,1.14
+2019,GIN,0.304
+2019,GNB,0.164
+2019,GUY,3.45
+2019,HTI,0.277
+2019,HND,1.127
+2019,HKG,5.59
+2019,HUN,5.046
+2019,ISL,9.864
+2019,IND,1.889
+2019,IDN,2.414
+2019,IRN,8.025
+2019,IRQ,4.562
+2019,IRL,7.624
+2019,ISR,6.815
+2019,ITA,5.699
+2019,JAM,2.78
+2019,JPN,8.781
+2019,JOR,2.311
+2019,KAZ,14.679
+2019,KEN,0.389
+2019,KIR,0.531
+2019,KWT,24.519
+2019,KGZ,1.431
+2019,LAO,2.709
+2019,LVA,3.992
+2019,LBN,4.438
+2019,ALSO,1.073
+2019,LBR,0.205
+2019,LBY,10.736
+2019,LIE,3.871
+2019,LTU,4.801
+2019,LUX,15.758
+2019,MAC,1.827
+2019,MDG,0.155
+2019,MWI,0.087
+2019,MYS,8.154
+2019,MDV,3.871
+2019,MLI,0.284
+2019,MLT,3.274
+2019,MHL,3.275
+2019,MRT,0.861
+2019,MUS,3.488
+2019,MEX,3.744
+2019,FSM,1.316
+2019,MDA,1.767
+2019,MCO,0
+2019,MNG,14.619
+2019,MNE,3.929
+2019,MSR,4.855
+2019,MAR,1.894
+2019,MOZ,0.234
+2019,MMR,0.634
+2019,NAME,1.68
+2019,NRU,4.521
+2019,NPL,0.471
+2019,NLD,8.783
+2019,NCL,19.879
+2019,NZL,7.437
+2019,NIC,0.784
+2019,NER,0.107
+2019,NGA,0.626
+2019,NIU,3.771
+2019,PRK,2.049
+2019,MKD,3.617
+2019,NOR,8.001
+2019,OMN,12.867
+2019,PAK,0.923
+2019,PLW,12.255
+2019,PSE,0.665
+2019,PAN,3.161
+2019,PNG,0.819
+2019,PRY,1.246
+2019,PER,1.778
+2019,PHL,1.306
+2019,POL,8.253
+2019,PRT,4.616
+2019,QAT,35.985
+2019,ROU,3.938
+2019,RUS,11.699
+2019,RWA,0.107
+2019,SHN,2.679
+2019,KNA,5.296
+2019,LCA,2.811
+2019,SPM,11.081
+2019,VCT,2.165
+2019,WSM,1.279
+2019,SMR,0
+2019,STP,0.666
+2019,SAU,19.737
+2019,SEN,0.795
+2019,SRB,5.983
+2019,SYC,5.686
+2019,SLE,0.125
+2019,SGP,5.758
+2019,SXM,16.019
+2019,SVK,6.193
+2019,SVN,6.645
+2019,SLB,0.434
+2019,SOME,0.04
+2019,ZAF,8.013
+2019,KOR,12.472
+2019,SSD,0.17
+2019,ESP,5.318
+2019,LKA,0.981
+2019,SDN,0.504
+2019,SURE,4.391
+2019,SWE,3.994
+2019,CHE,4.283
+2019,SYR,1.427
+2019,TWN,11.521
+2019,TJK,0.949
+2019,TZA,0.23
+2019,THA,3.953
+2019,TGO,0.293
+2019,TON,1.536
+2019,TO,26.832
+2019,TUN,2.519
+2019,TUR,4.824
+2019,TKM,10.607
+2019,TCA,8.415
+2019,TUV,1.001
+2019,UGA,0.133
+2019,UKR,5.02
+2019,ARE,23.835
+2019,GBR,5.462
+2019,USA,15.74
+2019,URY,1.893
+2019,UZB,3.288
+2019,VUT,0.542
+2019,VAT,0
+2019,VEN,3.024
+2019,VNM,3.569
+2019,WLF,2.186
+2019,YEM,0.375
+2019,ZMB,0.422
+2019,ZWE,0.637
+2020,AFG,0.305
+2020,,4.465
+2020,ALB,1.751
+2020,DZA,3.91
+2020,AND,4.808
+2020,AGO,0.502
+2020,AIA,9.153
+2020,ATA,0
+2020,ATG,6.682
+2020,ARG,3.705
+2020,ARM,2.423
+2020,ABW,8.353
+2020,AUS,15.453
+2020,AUT,6.974
+2020,AZE,3.515
+2020,BHS,5.372
+2020,BHR,25.311
+2020,BGD,0.559
+2020,BRB,4.394
+2020,BLR,6.13
+2020,BEL,7.88
+2020,BLZ,1.553
+2020,BEN,0.613
+2020,BMU,7.151
+2020,BTN,1.191
+2020,BOL,1.535
+2020,BES,4.339
+2020,BIH,6.278
+2020,BWA,2.18
+2020,BRA,2.085
+2020,VGB,5.212
+2020,BRN,25.132
+2020,BGR,5.235
+2020,BFA,0.241
+2020,BDI,0.06
+2020,KHM,1.161
+2020,CMR,0.366
+2020,CAN,13.8
+2020,CPV,0.887
+2020,CALF,0.045
+2020,TCD,0.137
+2020,CHL,4.154
+2020,CHN,7.659
+2020,CXR,0
+2020,COL,1.762
+2020,COM,0.473
+2020,COG,1.296
+2020,COK,3.868
+2020,CRI,1.32
+2020,CIV,0.411
+2020,HRV,4.118
+2020,CUB,1.883
+2020,CUW,9.542
+2020,CYP,5.584
+2020,CZE,8.707
+2020,COD,0.04
+2020,DNK,4.858
+2020,DJI,0.351
+2020,DMA,2.188
+2020,DOM,2.137
+2020,TLS,0.497
+2020,ECU,1.871
+2020,EGY,2.118
+2020,SLV,1.048
+2020,GNQ,3.462
+2020,ERI,0.18
+2020,EST,6.949
+2020,SWZ,0.925
+2020,ETH,0.152
+2020,FRO,14.463
+2020,FJI,1.131
+2020,FIN,6.824
+2020,FRA,4.366
+2020,PYF,2.803
+2020,GAB,2.586
+2020,GMB,0.272
+2020,GEO,2.808
+2020,DEU,7.767
+2020,GHA,0.596
+2020,GRC,5.291
+2020,GRL,9.584
+2020,GRD,2.341
+2020,GTM,0.999
+2020,GIN,0.341
+2020,GNB,0.147
+2020,GUY,3.999
+2020,HTI,0.222
+2020,HND,0.941
+2020,HKG,4.466
+2020,HUN,4.855
+2020,ISL,9.108
+2020,IND,1.734
+2020,IDN,2.229
+2020,IRN,7.779
+2020,IRQ,3.756
+2020,IRL,7.101
+2020,ISR,6.36
+2020,ITA,5.097
+2020,JAM,2.291
+2020,JPN,8.302
+2020,JOR,1.933
+2020,KAZ,13.461
+2020,KEN,0.423
+2020,KIR,0.521
+2020,KWT,22.409
+2020,KGZ,1.299
+2020,LAO,2.688
+2020,LVA,3.69
+2020,LBN,3.934
+2020,ALSO,0.964
+2020,LBR,0.151
+2020,LBY,7.074
+2020,LIE,3.66
+2020,LTU,4.8
+2020,LUX,12.798
+2020,MAC,1.528
+2020,MDG,0.14
+2020,MWI,0.097
+2020,MYS,8.11
+2020,MDV,3.205
+2020,MLI,0.302
+2020,MLT,3.099
+2020,MHL,3.374
+2020,MRT,0.915
+2020,MUS,2.933
+2020,MEX,3.51
+2020,FSM,1.307
+2020,MDA,1.699
+2020,MCO,0
+2020,MNG,11.255
+2020,MNE,3.827
+2020,MSR,4.857
+2020,MAR,1.76
+2020,MOZ,0.205
+2020,MMR,0.648
+2020,NAME,1.479
+2020,NRU,4.157
+2020,NPL,0.508
+2020,NLD,7.84
+2020,NCL,17.839
+2020,NZL,6.765
+2020,NIC,0.703
+2020,NER,0.116
+2020,NGA,0.594
+2020,NIU,3.733
+2020,PRK,1.926
+2020,MKD,3.294
+2020,NOR,7.664
+2020,OMN,14.401
+2020,PAK,0.883
+2020,PLW,11.807
+2020,PSE,0.663
+2020,PAN,2.404
+2020,PNG,0.792
+2020,PRY,1.113
+2020,PER,1.41
+2020,PHL,1.184
+2020,POL,7.87
+2020,PRT,4.049
+2020,QAT,37.133
+2020,ROU,3.808
+2020,RUS,11.214
+2020,RWA,0.104
+2020,SHN,3.363
+2020,KNA,4.843
+2020,LCA,2.698
+2020,SPM,10.504
+2020,VCT,2.346
+2020,WSM,1.125
+2020,SMR,0
+2020,STP,0.637
+2020,SAU,16.967
+2020,SEN,0.658
+2020,SRB,6.139
+2020,SYC,5.763
+2020,SLE,0.125
+2020,SGP,9.275
+2020,SXM,14.946
+2020,SVK,5.699
+2020,SVN,6.07
+2020,SLB,0.419
+2020,SOME,0.036
+2020,ZAF,7.395
+2020,KOR,11.527
+2020,SSD,0.16
+2020,ESP,4.51
+2020,LKA,0.95
+2020,SDN,0.462
+2020,SURE,4.807
+2020,SWE,3.538
+2020,CHE,3.963
+2020,SYR,1.267
+2020,TWN,11.409
+2020,TJK,0.974
+2020,TZA,0.23
+2020,THA,3.803
+2020,TGO,0.282
+2020,TON,1.74
+2020,TO,23.074
+2020,TUN,2.343
+2020,TUR,4.908
+2020,TKM,10.831
+2020,TCA,8.106
+2020,TUV,0.991
+2020,UGA,0.125
+2020,UKR,4.71
+2020,ARE,23.34
+2020,GBR,4.865
+2020,USA,14.034
+2020,URY,1.889
+2020,UZB,3.27
+2020,VUT,0.647
+2020,VAT,0
+2020,VEN,2.175
+2020,VNM,3.759
+2020,WLF,2.196
+2020,YEM,0.337
+2020,ZMB,0.43
+2020,ZWE,0.501
+2021,AFG,0.306
+2021,,4.655
+2021,ALB,1.718
+2021,DZA,4.08
+2021,AND,4.592
+2021,AGO,0.507
+2021,AIA,8.727
+2021,ATA,0
+2021,ATG,6.4
+2021,ARG,4.191
+2021,ARM,2.531
+2021,ABW,8.053
+2021,AUS,14.915
+2021,AUT,7.399
+2021,AZE,3.717
+2021,BHS,5.15
+2021,BHR,26.053
+2021,BGD,0.583
+2021,BRB,4.361
+2021,BLR,6.362
+2021,BEL,8.239
+2021,BLZ,1.755
+2021,BEN,0.64
+2021,BMU,6.873
+2021,BTN,1.366
+2021,BOL,1.799
+2021,BES,4.095
+2021,BIH,6.095
+2021,BWA,2.371
+2021,BRA,2.32
+2021,VGB,4.988
+2021,BRN,25.376
+2021,BGR,6.14
+2021,BFA,0.266
+2021,BDI,0.064
+2021,KHM,1.213
+2021,CMR,0.379
+2021,CAN,14.079
+2021,CPV,0.951
+2021,CALF,0.046
+2021,TCD,0.144
+2021,CHL,4.568
+2021,CHN,7.95
+2021,CXR,0
+2021,COL,1.859
+2021,COM,0.505
+2021,COG,1.243
+2021,COK,4.042
+2021,CRI,1.485
+2021,CIV,0.427
+2021,HRV,4.288
+2021,CUB,1.86
+2021,CUW,9.145
+2021,CYP,5.65
+2021,CZE,9.197
+2021,COD,0.041
+2021,DNK,5.058
+2021,DJI,0.411
+2021,DMA,2.096
+2021,DOM,2.139
+2021,TLS,0.502
+2021,ECU,2.232
+2021,EGY,2.26
+2021,SLV,1.186
+2021,GNQ,3.444
+2021,ERI,0.193
+2021,EST,7.842
+2021,SWZ,0.949
+2021,ETH,0.157
+2021,FRO,13.924
+2021,FJI,1.174
+2021,FIN,6.855
+2021,FRA,4.754
+2021,PYF,2.905
+2021,GAB,2.512
+2021,GMB,0.287
+2021,GEO,2.942
+2021,DEU,8.138
+2021,GHA,0.639
+2021,GRC,5.51
+2021,GRL,10.072
+2021,GRD,2.647
+2021,GTM,1.118
+2021,GIN,0.36
+2021,GNB,0.156
+2021,GUY,4.43
+2021,HTI,0.212
+2021,HND,1.054
+2021,HKG,4.373
+2021,HUN,5.002
+2021,ISL,9.478
+2021,IND,1.9
+2021,IDN,2.25
+2021,IRN,7.826
+2021,IRQ,3.873
+2021,IRL,7.53
+2021,ISR,6.163
+2021,ITA,5.693
+2021,JAM,2.337
+2021,JPN,8.523
+2021,JOR,2.033
+2021,KAZ,13.291
+2021,KEN,0.461
+2021,KIR,0.534
+2021,KWT,24.301
+2021,KGZ,1.445
+2021,LAO,3.147
+2021,LVA,3.864
+2021,LBN,4.197
+2021,ALSO,1.087
+2021,LBR,0.167
+2021,LBY,9.491
+2021,LIE,3.735
+2021,LTU,4.963
+2021,LUX,13.185
+2021,MAC,1.557
+2021,MDG,0.148
+2021,MWI,0.104
+2021,MYS,8.306
+2021,MDV,3.299
+2021,MLI,0.316
+2021,MLT,3.05
+2021,MHL,3.635
+2021,MRT,0.965
+2021,MUS,3.149
+2021,MEX,3.7
+2021,FSM,1.352
+2021,MDA,1.828
+2021,MCO,0
+2021,MNG,11.427
+2021,MNE,3.671
+2021,MSR,4.773
+2021,MAR,1.959
+2021,MOZ,0.224
+2021,MMR,0.662
+2021,NAME,1.475
+2021,NRU,4.271
+2021,NPL,0.52
+2021,NLD,7.994
+2021,NCL,15.763
+2021,NZL,6.69
+2021,NIC,0.786
+2021,NER,0.12
+2021,NGA,0.614
+2021,NIU,3.908
+2021,PRK,1.977
+2021,MKD,3.656
+2021,NOR,7.594
+2021,OMN,15.677
+2021,PAK,0.966
+2021,PLW,12.285
+2021,PSE,0.668
+2021,PAN,2.691
+2021,PNG,0.799
+2021,PRY,1.382
+2021,PER,1.675
+2021,PHL,1.254
+2021,POL,8.643
+2021,PRT,3.881
+2021,QAT,39.884
+2021,ROU,3.994
+2021,RUS,11.798
+2021,RWA,0.115
+2021,SHN,3.252
+2021,KNA,4.67
+2021,LCA,2.594
+2021,SPM,10.165
+2021,VCT,2.267
+2021,WSM,1.153
+2021,SMR,0
+2021,STP,0.653
+2021,SAU,17.564
+2021,SEN,0.684
+2021,SRB,6.017
+2021,SYC,6.223
+2021,SLE,0.132
+2021,SGP,9.397
+2021,SXM,14.264
+2021,SVK,6.455
+2021,SVN,6.162
+2021,SLB,0.427
+2021,SOME,0.038
+2021,ZAF,7.166
+2021,KOR,11.886
+2021,SSD,0.172
+2021,ESP,4.849
+2021,LKA,0.924
+2021,SDN,0.486
+2021,SURE,6.008
+2021,SWE,3.681
+2021,CHE,4.118
+2021,SYR,1.276
+2021,TWN,12.205
+2021,TJK,1.024
+2021,TZA,0.242
+2021,THA,3.732
+2021,TGO,0.296
+2021,TON,1.803
+2021,TO,23.29
+2021,TUN,2.874
+2021,TUR,5.34
+2021,TKM,11.034
+2021,TCA,7.665
+2021,TUV,1.021
+2021,UGA,0.132
+2021,UKR,4.828
+2021,ARE,25.333
+2021,GBR,5.164
+2021,USA,14.932
+2021,URY,2.365
+2021,UZB,3.415
+2021,VUT,0.659
+2021,VAT,0
+2021,VEN,2.539
+2021,VNM,3.617
+2021,WLF,2.296
+2021,YEM,0.351
+2021,ZMB,0.445
+2021,ZWE,0.525
+2022,AFG,0.295
+2022,,4.658
+2022,ALB,1.743
+2022,DZA,3.927
+2022,AND,4.617
+2022,AGO,0.452
+2022,AIA,8.753
+2022,ATA,0
+2022,ATG,6.422
+2022,ARG,4.238
+2022,ARM,2.305
+2022,ABW,8.133
+2022,AUS,14.985
+2022,AUT,6.878
+2022,AZE,3.675
+2022,BHS,5.171
+2022,BHR,25.672
+2022,BGD,0.596
+2022,BRB,4.377
+2022,BLR,6.167
+2022,BEL,7.688
+2022,BLZ,1.789
+2022,BEN,0.631
+2022,BMU,6.937
+2022,BTN,1.349
+2022,BOL,1.758
+2022,BES,4.083
+2022,BIH,6.103
+2022,BWA,2.839
+2022,BRA,2.245
+2022,VGB,5.004
+2022,BRN,23.95
+2022,BGR,6.804
+2022,BFA,0.263
+2022,BDI,0.062
+2022,KHM,1.19
+2022,CMR,0.343
+2022,CAN,14.249
+2022,CPV,0.959
+2022,CALF,0.041
+2022,TCD,0.134
+2022,CHL,4.304
+2022,CHN,7.993
+2022,CXR,0
+2022,COL,1.922
+2022,COM,0.493
+2022,COG,1.245
+2022,COK,3.995
+2022,CRI,1.523
+2022,CIV,0.417
+2022,HRV,4.349
+2022,CUB,1.866
+2022,CUW,9.189
+2022,CYP,5.617
+2022,CZE,9.336
+2022,COD,0.036
+2022,DNK,4.94
+2022,DJI,0.404
+2022,DMA,2.106
+2022,DOM,2.105
+2022,TLS,0.499
+2022,ECU,2.312
+2022,EGY,2.333
+2022,SLV,1.217
+2022,GNQ,3.031
+2022,ERI,0.189
+2022,EST,7.776
+2022,SWZ,1.053
+2022,ETH,0.155
+2022,FRO,14.085
+2022,FJI,1.155
+2022,FIN,6.527
+2022,FRA,4.604
+2022,PYF,2.851
+2022,GAB,2.388
+2022,GMB,0.285
+2022,GEO,2.963
+2022,DEU,7.984
+2022,GHA,0.622
+2022,GRC,5.745
+2022,GRL,10.474
+2022,GRD,2.713
+2022,GTM,1.076
+2022,GIN,0.357
+2022,GNB,0.155
+2022,GUY,4.374
+2022,HTI,0.211
+2022,HND,1.07
+2022,HKG,4.082
+2022,HUN,4.45
+2022,ISL,9.5
+2022,IND,1.997
+2022,IDN,2.646
+2022,IRN,7.799
+2022,IRQ,4.025
+2022,IRL,7.721
+2022,ISR,6.209
+2022,ITA,5.727
+2022,JAM,2.295
+2022,JPN,8.502
+2022,JOR,2.03
+2022,KAZ,13.98
+2022,KEN,0.46
+2022,KIR,0.518
+2022,KWT,25.578
+2022,KGZ,1.425
+2022,LAO,3.08
+2022,LVA,3.562
+2022,LBN,4.354
+2022,ALSO,1.359
+2022,LBR,0.165
+2022,LBY,9.242
+2022,LIE,3.81
+2022,LTU,4.606
+2022,LUX,11.618
+2022,MAC,1.513
+2022,MDG,0.149
+2022,MWI,0.103
+2022,MYS,8.577
+2022,MDV,3.248
+2022,MLI,0.312
+2022,MLT,3.104
+2022,MHL,3.635
+2022,MRT,0.957
+2022,MUS,3.27
+2022,MEX,4.015
+2022,FSM,1.324
+2022,MDA,1.657
+2022,MCO,0
+2022,MNG,11.151
+2022,MNE,3.656
+2022,MSR,4.845
+2022,MAR,1.826
+2022,MOZ,0.243
+2022,MMR,0.645
+2022,NAME,1.54
+2022,NRU,4.17
+2022,NPL,0.507
+2022,NLD,7.137
+2022,NCL,17.641
+2022,NZL,6.212
+2022,NIC,0.799
+2022,NER,0.117
+2022,NGA,0.589
+2022,NIU,3.873
+2022,PRK,1.951
+2022,MKD,3.625
+2022,NOR,7.509
+2022,OMN,15.73
+2022,PAK,0.849
+2022,PLW,12.124
+2022,PSE,0.666
+2022,PAN,2.699
+2022,PNG,0.771
+2022,PRY,1.33
+2022,PER,1.789
+2022,PHL,1.301
+2022,POL,8.107
+2022,PRT,4.051
+2022,QAT,37.601
+2022,ROU,3.74
+2022,RUS,11.417
+2022,RWA,0.112
+2022,SHN,3.299
+2022,KNA,4.708
+2022,LCA,2.615
+2022,SPM,10.293
+2022,VCT,2.296
+2022,WSM,1.122
+2022,SMR,0
+2022,STP,0.582
+2022,SAU,18.197
+2022,SEN,0.674
+2022,SRB,6.025
+2022,SYC,6.15
+2022,SLE,0.131
+2022,SGP,8.912
+2022,SXM,14.352
+2022,SVK,6.052
+2022,SVN,5.998
+2022,SLB,0.412
+2022,SOME,0.037
+2022,ZAF,6.746
+2022,KOR,11.599
+2022,SSD,0.168
+2022,ESP,5.164
+2022,LKA,0.794
+2022,SDN,0.47
+2022,SURE,5.803
+2022,SWE,3.607
+2022,CHE,4.048
+2022,SYR,1.249
+2022,TWN,11.631
+2022,TJK,1.006
+2022,TZA,0.238
+2022,THA,3.776
+2022,TGO,0.291
+2022,TON,1.769
+2022,TO,22.424
+2022,TUN,2.879
+2022,TUR,5.105
+2022,TKM,11.034
+2022,TCA,7.637
+2022,TUV,1.0
+2022,UGA,0.127
+2022,UKR,3.558
+2022,ARE,25.833
+2022,GBR,4.72
+2022,USA,14.95
+2022,URY,2.306
+2022,UZB,3.483
+2022,VUT,0.636
+2022,VAT,0
+2022,VEN,2.717
+2022,VNM,3.5
+2022,WLF,2.282
+2022,YEM,0.337
+2022,ZMB,0.446
+2022,ZWE,0.543
diff --git a/examples/ReadKml.py b/examples/read_kml.py
old mode 100644
new mode 100755
similarity index 97%
rename from examples/ReadKml.py
rename to examples/read_kml.py
index 77f67266..d8d78a32
--- a/examples/ReadKml.py
+++ b/examples/read_kml.py
@@ -30,7 +30,7 @@
# Create the KML object to store the parsed result
# Read in the KML string
-k = kml.KML.class_from_string(doc.encode("utf-8"))
+k = kml.KML.from_string(doc.encode("utf-8"))
# Next we perform some simple sanity checks
diff --git a/examples/shp2kml.py b/examples/shp2kml.py
new file mode 100755
index 00000000..1b0a1634
--- /dev/null
+++ b/examples/shp2kml.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+import csv
+import pathlib
+import random
+
+import shapefile
+from pygeoif.factories import force_3d
+from pygeoif.factories import shape
+
+import fastkml
+import fastkml.containers
+import fastkml.features
+import fastkml.styles
+from fastkml.enums import AltitudeMode
+from fastkml.enums import ColorMode
+from fastkml.geometry import create_kml_geometry
+
+examples_dir = pathlib.Path(__file__).parent
+
+shp = shapefile.Reader(examples_dir / "ne_110m_admin_0_countries.shp")
+
+co2_csv = pathlib.Path(examples_dir / "owid-co2-data.csv")
+
+co2_data = {}
+
+with co2_csv.open() as csvfile:
+ reader = csv.DictReader(csvfile)
+ for row in reader:
+ if row["year"] == "2020":
+ co2_data[row["iso_code"]] = (
+ float(row["co2_per_capita"]) if row["co2_per_capita"] else 0
+ )
+
+document = fastkml.containers.Document()
+
+for feature in shp.__geo_interface__["features"]:
+ geometry = shape(feature["geometry"])
+ co2_emission = co2_data.get(feature["properties"]["ADM0_A3"], 0)
+ geometry = force_3d(geometry, co2_emission * 100_000)
+ kml_geometry = create_kml_geometry(
+ geometry,
+ extrude=True,
+ altitude_mode=AltitudeMode.relative_to_ground,
+ )
+ color = random.randint(0, 0xFFFFFF)
+ style = fastkml.styles.Style(
+ id=feature["properties"]["ADM0_A3"],
+ styles=[
+ fastkml.styles.LineStyle(color=f"55{color:06X}", width=2),
+ fastkml.styles.PolyStyle(
+ color_mode=ColorMode.random,
+ color=f"88{color:06X}",
+ fill=True,
+ outline=True,
+ ),
+ ],
+ )
+
+ style_url = fastkml.styles.StyleUrl(url=f"#{feature['properties']['ADM0_A3']}")
+ placemark = fastkml.features.Placemark(
+ name=feature["properties"]["NAME"],
+ description=feature["properties"]["FORMAL_EN"],
+ kml_geometry=kml_geometry,
+ styles=[style],
+ )
+ document.append(placemark)
+
+kml = fastkml.KML(features=[document])
+
+outfile = pathlib.Path("co2_per_capita_2020.kml")
+with outfile.open("w") as f:
+ f.write(kml.to_string(prettyprint=True, precision=3))
diff --git a/examples/shp2kml_timed.py b/examples/shp2kml_timed.py
new file mode 100755
index 00000000..2f7dd343
--- /dev/null
+++ b/examples/shp2kml_timed.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python
+import csv
+import datetime
+import pathlib
+import random
+
+import shapefile
+from pygeoif.factories import force_3d
+from pygeoif.factories import shape
+
+import fastkml
+import fastkml.containers
+import fastkml.features
+import fastkml.styles
+import fastkml.times
+from fastkml.enums import AltitudeMode
+from fastkml.enums import DateTimeResolution
+from fastkml.geometry import create_kml_geometry
+
+examples_dir = pathlib.Path(__file__).parent
+
+shp = shapefile.Reader(examples_dir / "ne_110m_admin_0_countries.shp")
+
+co2_csv = pathlib.Path(examples_dir / "owid-co2-data.csv")
+
+co2_pa = {str(i): {} for i in range(1995, 2023)}
+
+with co2_csv.open() as csvfile:
+ reader = csv.DictReader(csvfile)
+ for row in reader:
+ if row["year"] >= "1995":
+ co2_pa[row["year"]][row["iso_code"]] = (
+ float(row["co2_per_capita"]) if row["co2_per_capita"] else 0
+ )
+
+styles = []
+folders = []
+for feature in shp.__geo_interface__["features"]:
+ iso3_code = feature["properties"]["ADM0_A3"]
+ geometry = shape(feature["geometry"])
+ color = random.randint(0, 0xFFFFFF)
+ styles.append(
+ fastkml.styles.Style(
+ id=iso3_code,
+ styles=[
+ fastkml.styles.LineStyle(color=f"33{color:06X}", width=2),
+ fastkml.styles.PolyStyle(
+ color=f"88{color:06X}",
+ fill=True,
+ outline=True,
+ ),
+ ],
+ ),
+ )
+ style_url = fastkml.styles.StyleUrl(url=f"#{iso3_code}")
+ folder = fastkml.containers.Folder(name=feature["properties"]["NAME"])
+ co2_growth = 0
+ for year in range(1995, 2023):
+ co2_year = co2_pa[str(year)].get(iso3_code, 0)
+ co2_growth += co2_year
+
+ kml_geometry = create_kml_geometry(
+ force_3d(geometry, co2_growth * 5_000),
+ extrude=True,
+ altitude_mode=AltitudeMode.relative_to_ground,
+ )
+ timespan = fastkml.times.TimeSpan(
+ begin=fastkml.times.KmlDateTime(
+ datetime.date(year, 1, 1),
+ resolution=DateTimeResolution.year_month,
+ ),
+ end=fastkml.times.KmlDateTime(
+ datetime.date(year, 12, 31),
+ resolution=DateTimeResolution.year_month,
+ ),
+ )
+ placemark = fastkml.features.Placemark(
+ name=f"{feature['properties']['NAME']} - {year}",
+ description=feature["properties"]["FORMAL_EN"],
+ kml_geometry=kml_geometry,
+ style_url=style_url,
+ times=timespan,
+ )
+ folder.features.append(placemark)
+ folders.append(folder)
+
+document = fastkml.containers.Document(features=folders, styles=styles)
+kml = fastkml.KML(features=[document])
+
+outfile = pathlib.Path("co2_growth_1995_2022.kml")
+with outfile.open("w") as f:
+ f.write(kml.to_string(prettyprint=True, precision=3))
diff --git a/examples/UsageExamples.py b/examples/simple_example.py
old mode 100644
new mode 100755
similarity index 57%
rename from examples/UsageExamples.py
rename to examples/simple_example.py
index 82b98379..c5b9e139
--- a/examples/UsageExamples.py
+++ b/examples/simple_example.py
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+import pathlib
from fastkml import kml
@@ -8,14 +9,15 @@ def print_child_features(element, depth=0):
if not getattr(element, "features", None):
return
for feature in element.features:
- print(" " * depth + feature.name)
+ print(" " * depth, feature.name)
print_child_features(feature, depth + 1)
if __name__ == "__main__":
- fname = "KML_Samples.kml"
+ examples_dir = pathlib.Path(__file__).parent
+ fname = pathlib.Path(examples_dir / "KML_Samples.kml")
- with open(fname, encoding="utf-8") as kml_file:
- k = kml.KML.class_from_string(kml_file.read().encode("utf-8"))
+ with fname.open(encoding="utf-8") as kml_file:
+ k = kml.KML.from_string(kml_file.read().encode("utf-8"))
print_child_features(k)
diff --git a/examples/transform_cascading_style.py b/examples/transform_cascading_style.py
new file mode 100755
index 00000000..42d7df89
--- /dev/null
+++ b/examples/transform_cascading_style.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python
+import pathlib
+from typing import Any
+from typing import Dict
+from typing import Optional
+
+from fastkml import KML
+from fastkml import Document
+from fastkml import Style
+from fastkml import config
+from fastkml.helpers import xml_subelement
+from fastkml.helpers import xml_subelement_kwarg
+from fastkml.helpers import xml_subelement_list
+from fastkml.helpers import xml_subelement_list_kwarg
+from fastkml.kml_base import _BaseObject
+from fastkml.registry import RegistryItem
+from fastkml.registry import registry
+from fastkml.utils import find
+
+examples_dir = pathlib.Path(__file__).parent
+
+
+class CascadingStyle(_BaseObject):
+ """
+ CascadingStyle.
+
+ The ``
`` is an undocumented element that is created in
+ Google Earth Web that is unsupported by Google Earth Pro
+ """
+
+ _default_nsid = config.GX
+
+ def __init__(
+ self,
+ ns: Optional[str] = None,
+ name_spaces: Optional[Dict[str, str]] = None,
+ id: Optional[str] = None,
+ target_id: Optional[str] = None,
+ style: Optional[Style] = None,
+ **kwargs: Any,
+ ) -> None:
+ """Initialize the CascadingStyle object."""
+ self.style = style
+ super().__init__(ns, name_spaces, id, target_id, **kwargs)
+
+
+registry.register(
+ CascadingStyle,
+ RegistryItem(
+ ns_ids=("kml",),
+ attr_name="style",
+ node_name="Style",
+ classes=(Style,),
+ get_kwarg=xml_subelement_kwarg,
+ set_element=xml_subelement,
+ ),
+)
+
+
+registry.register(
+ Document,
+ RegistryItem(
+ ns_ids=("gx",),
+ attr_name="gx_cascading_style",
+ node_name="CascadingStyle",
+ classes=(CascadingStyle,),
+ get_kwarg=xml_subelement_list_kwarg,
+ set_element=xml_subelement_list,
+ ),
+)
+
+cs_kml = KML.parse(examples_dir / "gx_cascading_style.kml")
+document = find(cs_kml, of_type=Document)
+for cascading_style in document.gx_cascading_style:
+ kml_style = cascading_style.style
+ kml_style.id = cascading_style.id
+ document.styles.append(kml_style)
+
+document.gx_cascading_style = []
+print(cs_kml.to_string(prettyprint=True))
diff --git a/fastkml/__init__.py b/fastkml/__init__.py
index a6ca8b7e..d0cb773f 100644
--- a/fastkml/__init__.py
+++ b/fastkml/__init__.py
@@ -25,9 +25,9 @@
functionality that KML on google earth provides.
"""
from fastkml.about import __version__ # noqa: F401
-from fastkml.atom import Author
-from fastkml.atom import Contributor
-from fastkml.atom import Link
+from fastkml.atom import Author as AtomAuthor
+from fastkml.atom import Contributor as AtomContributor
+from fastkml.atom import Link as AtomLink
from fastkml.containers import Document
from fastkml.containers import Folder
from fastkml.data import Data
@@ -35,7 +35,14 @@
from fastkml.data import Schema
from fastkml.data import SchemaData
from fastkml.features import Placemark
+from fastkml.geometry import LinearRing
+from fastkml.geometry import LineString
+from fastkml.geometry import MultiGeometry
+from fastkml.geometry import Point
+from fastkml.geometry import Polygon
from fastkml.kml import KML
+from fastkml.links import Icon
+from fastkml.links import Link
from fastkml.overlays import GroundOverlay
from fastkml.overlays import PhotoOverlay
from fastkml.styles import BalloonStyle
@@ -72,9 +79,16 @@
"PolyStyle",
"LabelStyle",
"BalloonStyle",
+ "AtomLink",
+ "Icon",
"Link",
- "Author",
- "Contributor",
+ "Point",
+ "LineString",
+ "LinearRing",
+ "Polygon",
+ "MultiGeometry",
+ "AtomAuthor",
+ "AtomContributor",
"Camera",
"LookAt",
]
diff --git a/fastkml/about.py b/fastkml/about.py
index ee30bf21..c3030e31 100644
--- a/fastkml/about.py
+++ b/fastkml/about.py
@@ -18,4 +18,4 @@
The only purpose of this module is to provide a version number for the package.
"""
-__version__ = "1.0.a13"
+__version__ = "1.0.0b1"
diff --git a/fastkml/atom.py b/fastkml/atom.py
index 1a75b8dd..fde10d0b 100644
--- a/fastkml/atom.py
+++ b/fastkml/atom.py
@@ -40,7 +40,6 @@
from fastkml import config
from fastkml.base import _XMLObject
-from fastkml.config import ATOMNS as NS
from fastkml.helpers import attribute_int_kwarg
from fastkml.helpers import attribute_text_kwarg
from fastkml.helpers import int_attribute
@@ -83,11 +82,11 @@ class Link(_AtomObject):
title, and length.
"""
- href: Optional[str]
- rel: Optional[str]
- type: Optional[str]
- hreflang: Optional[str]
- title: Optional[str]
+ href: str
+ rel: str
+ type: str
+ hreflang: str
+ title: str
length: Optional[int]
def __init__(
@@ -134,11 +133,11 @@ def __init__(
"""
super().__init__(ns=ns, name_spaces=name_spaces, **kwargs)
- self.href = href
- self.rel = rel
- self.type = type
- self.hreflang = hreflang
- self.title = title
+ self.href = href or ""
+ self.rel = rel or ""
+ self.type = type or ""
+ self.hreflang = hreflang or ""
+ self.title = title or ""
self.length = length
def __repr__(self) -> str:
@@ -259,9 +258,9 @@ class _Person(_AtomObject):
"""
- name: Optional[str]
- uri: Optional[str]
- email: Optional[str]
+ name: str
+ uri: str
+ email: str
def __init__(
self,
@@ -286,9 +285,9 @@ def __init__(
"""
super().__init__(ns=ns, name_spaces=name_spaces, **kwargs)
- self.name = name
- self.uri = uri
- self.email = email
+ self.name = name.strip() if name else ""
+ self.uri = uri.strip() if uri else ""
+ self.email = email.strip() if email else ""
def __repr__(self) -> str:
"""
@@ -373,4 +372,4 @@ class Contributor(_Person):
"""
-__all__ = ["Author", "Contributor", "Link", "NS"]
+__all__ = ["Author", "Contributor", "Link"]
diff --git a/fastkml/base.py b/fastkml/base.py
index 15c00da7..f67f7de1 100644
--- a/fastkml/base.py
+++ b/fastkml/base.py
@@ -115,10 +115,7 @@ def __eq__(self, other: object) -> bool:
True if the objects are equal, False otherwise.
"""
- if type(self) is not type(other):
- return False
- assert isinstance(other, type(self)) # noqa: S101
- return self.__dict__ == other.__dict__
+ return self.__dict__ == other.__dict__ if type(self) is type(other) else False
def etree_element(
self,
@@ -152,6 +149,7 @@ def etree_element(
node_name=item.node_name,
precision=precision,
verbosity=verbosity,
+ default=item.default,
)
return element
@@ -345,7 +343,7 @@ def class_from_element(
)
@classmethod
- def class_from_string(
+ def from_string(
cls,
string: str,
*,
diff --git a/fastkml/containers.py b/fastkml/containers.py
index bf1fa398..95dee533 100644
--- a/fastkml/containers.py
+++ b/fastkml/containers.py
@@ -44,6 +44,7 @@
from fastkml.styles import StyleUrl
from fastkml.times import TimeSpan
from fastkml.times import TimeStamp
+from fastkml.utils import find_all
from fastkml.views import Camera
from fastkml.views import LookAt
from fastkml.views import Region
@@ -306,7 +307,14 @@ def get_style_by_url(self, style_url: str) -> Optional[Union[Style, StyleMap]]:
"""
id_ = urlparse.urlparse(style_url).fragment
- return next((style for style in self.styles if style.id == id_), None)
+ return next(
+ find_all( # type: ignore[arg-type]
+ self,
+ of_type=(Style, StyleMap),
+ id=id_,
+ ),
+ None,
+ )
registry.register(
diff --git a/fastkml/data.py b/fastkml/data.py
index 23dafdfb..9d02a80b 100644
--- a/fastkml/data.py
+++ b/fastkml/data.py
@@ -156,6 +156,17 @@ def __bool__(self) -> bool:
return bool(self.name) and bool(self.type)
+registry.register(
+ SimpleField,
+ RegistryItem(
+ ns_ids=("kml",),
+ attr_name="display_name",
+ node_name="displayName",
+ classes=(str,),
+ get_kwarg=subelement_text_kwarg,
+ set_element=text_subelement,
+ ),
+)
registry.register(
SimpleField,
RegistryItem(
@@ -178,17 +189,6 @@ def __bool__(self) -> bool:
set_element=enum_attribute,
),
)
-registry.register(
- SimpleField,
- RegistryItem(
- ns_ids=("kml",),
- attr_name="display_name",
- node_name="displayName",
- classes=(str,),
- get_kwarg=subelement_text_kwarg,
- set_element=text_subelement,
- ),
-)
class Schema(_BaseObject):
@@ -409,13 +409,12 @@ def __bool__(self) -> bool:
set_element=text_attribute,
),
)
-
registry.register(
Data,
RegistryItem(
- ns_ids=("", "kml"),
- attr_name="value",
- node_name="value",
+ ns_ids=("kml",),
+ attr_name="display_name",
+ node_name="displayName",
classes=(str,),
get_kwarg=subelement_text_kwarg,
set_element=text_subelement,
@@ -424,9 +423,9 @@ def __bool__(self) -> bool:
registry.register(
Data,
RegistryItem(
- ns_ids=("kml",),
- attr_name="display_name",
- node_name="displayName",
+ ns_ids=("", "kml"),
+ attr_name="value",
+ node_name="value",
classes=(str,),
get_kwarg=subelement_text_kwarg,
set_element=text_subelement,
diff --git a/fastkml/enums.py b/fastkml/enums.py
index 407efed5..d75ff298 100644
--- a/fastkml/enums.py
+++ b/fastkml/enums.py
@@ -77,7 +77,7 @@ def _missing_(cls, value: object) -> "RelaxedEnum":
class Verbosity(Enum):
"""Enum to represent the different verbosity levels."""
- quiet = 0
+ terse = 0
normal = 1
verbose = 2
@@ -99,34 +99,35 @@ class AltitudeMode(RelaxedEnum):
Specifies how altitude components in the element are interpreted.
Possible values are
- - clampToGround - (default) Indicates to ignore an altitude specification
- (for example, in the tag).
- - relativeToGround - Sets the altitude of the element relative to the actual
- ground elevation of a particular location.
- For example, if the ground elevation of a location is exactly at sea level
- and the altitude for a point is set to 9 meters,
- then the elevation for the icon of a point placemark elevation is 9 meters
- with this mode.
- However, if the same coordinate is set over a location where the ground
- elevation is 10 meters above sea level, then the elevation of the coordinate
- is 19 meters.
- A typical use of this mode is for placing telephone poles or a ski lift.
- - absolute - Sets the altitude of the coordinate relative to sea level,
- regardless of the actual elevation of the terrain beneath the element.
- For example, if you set the altitude of a coordinate to 10 meters with an
- absolute altitude mode, the icon of a point placemark will appear to be at
- ground level if the terrain beneath is also 10 meters above sea level.
- If the terrain is 3 meters above sea level, the placemark will appear elevated
- above the terrain by 7 meters.
- A typical use of this mode is for aircraft placement.
- - relativeToSeaFloor - Interprets the altitude as a value in meters above the
- sea floor.
- If the point is above land rather than sea, the altitude will be interpreted
- as being above the ground.
- - clampToSeaFloor - The altitude specification is ignored, and the point will be
- positioned on the sea floor.
- If the point is on land rather than at sea, the point will be positioned on
- the ground.
+
+ - clampToGround - (default) Indicates to ignore an altitude specification
+ (for example, in the tag).
+ - relativeToGround - Sets the altitude of the element relative to the actual
+ ground elevation of a particular location.
+ For example, if the ground elevation of a location is exactly at sea level
+ and the altitude for a point is set to 9 meters,
+ then the elevation for the icon of a point placemark elevation is 9 meters
+ with this mode.
+ However, if the same coordinate is set over a location where the ground
+ elevation is 10 meters above sea level, then the elevation of the coordinate
+ is 19 meters.
+ A typical use of this mode is for placing telephone poles or a ski lift.
+ - absolute - Sets the altitude of the coordinate relative to sea level,
+ regardless of the actual elevation of the terrain beneath the element.
+ For example, if you set the altitude of a coordinate to 10 meters with an
+ absolute altitude mode, the icon of a point placemark will appear to be at
+ ground level if the terrain beneath is also 10 meters above sea level.
+ If the terrain is 3 meters above sea level, the placemark will appear elevated
+ above the terrain by 7 meters.
+ A typical use of this mode is for aircraft placement.
+ - relativeToSeaFloor - Interprets the altitude as a value in meters above the
+ sea floor.
+ If the point is above land rather than sea, the altitude will be interpreted
+ as being above the ground.
+ - clampToSeaFloor - The altitude specification is ignored, and the point will be
+ positioned on the sea floor.
+ If the point is on land rather than at sea, the point will be positioned on
+ the ground.
The Values relativeToSeaFloor and clampToSeaFloor are not part of the KML definition
but of the a KML extension in the Google extension namespace,
@@ -139,6 +140,15 @@ class AltitudeMode(RelaxedEnum):
clamp_to_sea_floor = "clampToSeaFloor"
relative_to_sea_floor = "relativeToSeaFloor"
+ def get_ns_id(self) -> str:
+ """Get the namespace for the altitude mode."""
+ if self in (
+ AltitudeMode.clamp_to_sea_floor,
+ AltitudeMode.relative_to_sea_floor,
+ ):
+ return "gx"
+ return "kml"
+
@unique
class DataType(RelaxedEnum):
@@ -216,9 +226,9 @@ class Shape(RelaxedEnum):
The PhotoOverlay is projected onto the .
The can be one of the following:
- - rectangle (default) - for an ordinary photo
- - cylinder - for panoramas, which can be either partial or full cylinders
- - sphere - for spherical panoramas
+ - rectangle (default) - for an ordinary photo
+ - cylinder - for panoramas, which can be either partial or full cylinders
+ - sphere - for spherical panoramas
"""
rectangle = "rectangle"
diff --git a/fastkml/features.py b/fastkml/features.py
index 4f809f0f..08e6ed24 100644
--- a/fastkml/features.py
+++ b/fastkml/features.py
@@ -185,6 +185,7 @@ def __bool__(self) -> bool:
classes=(int,),
get_kwarg=attribute_int_kwarg,
set_element=int_attribute,
+ default=2,
),
)
@@ -353,6 +354,7 @@ def __repr__(self) -> str:
classes=(bool,),
get_kwarg=subelement_bool_kwarg,
set_element=bool_subelement,
+ default=True,
),
)
registry.register(
@@ -364,15 +366,16 @@ def __repr__(self) -> str:
classes=(bool,),
get_kwarg=subelement_bool_kwarg,
set_element=bool_subelement,
+ default=False,
),
)
registry.register(
_Feature,
RegistryItem(
ns_ids=("atom",),
- attr_name="atom_link",
- node_name="atom:link",
- classes=(atom.Link,),
+ attr_name="atom_author",
+ node_name="atom:author",
+ classes=(atom.Author,),
get_kwarg=xml_subelement_kwarg,
set_element=xml_subelement,
),
@@ -381,9 +384,9 @@ def __repr__(self) -> str:
_Feature,
RegistryItem(
ns_ids=("atom",),
- attr_name="atom_author",
- node_name="atom:author",
- classes=(atom.Author,),
+ attr_name="atom_link",
+ node_name="atom:link",
+ classes=(atom.Link,),
get_kwarg=xml_subelement_kwarg,
set_element=xml_subelement,
),
@@ -628,7 +631,7 @@ def __init__(
msg = "You can only specify one of kml_geometry or geometry"
raise ValueError(msg)
if geometry:
- kml_geometry = create_kml_geometry( # type: ignore[assignment]
+ kml_geometry = create_kml_geometry(
geometry=geometry,
ns=ns,
name_spaces=name_spaces,
@@ -745,7 +748,8 @@ class NetworkLink(_Feature):
For example, Google Earth would fly to the view of the parent Document,
not the of the Placemarks contained within the Document.
(required)
- https://developers.google.com/kml/documentation/kmlreference#link
+
+ https://developers.google.com/kml/documentation/kmlreference#networklink
"""
refresh_visibility: Optional[bool]
@@ -921,6 +925,7 @@ def __bool__(self) -> bool:
classes=(bool,),
get_kwarg=subelement_bool_kwarg,
set_element=bool_subelement,
+ default=False,
),
)
registry.register(
@@ -932,6 +937,7 @@ def __bool__(self) -> bool:
classes=(bool,),
get_kwarg=subelement_bool_kwarg,
set_element=bool_subelement,
+ default=False,
),
)
registry.register(
diff --git a/fastkml/geometry.py b/fastkml/geometry.py
index 6b314140..50dd48e5 100644
--- a/fastkml/geometry.py
+++ b/fastkml/geometry.py
@@ -32,9 +32,11 @@
from typing import Final
from typing import Iterable
from typing import List
+from typing import NoReturn
from typing import Optional
from typing import Sequence
from typing import Tuple
+from typing import Type
from typing import Union
from typing import cast
@@ -63,7 +65,6 @@
from fastkml.helpers import xml_subelement_list_kwarg
from fastkml.kml_base import _BaseObject
from fastkml.registry import RegistryItem
-from fastkml.registry import known_types
from fastkml.registry import registry
from fastkml.types import Element
@@ -93,6 +94,8 @@
MsgMutualExclusive: Final = "Geometry and kml coordinates are mutually exclusive"
+xml_attrs = {"ns", "name_spaces", "id", "target_id"}
+
def handle_invalid_geometry_error(
*,
@@ -138,6 +141,7 @@ def coordinates_subelement(
node_name: str, # noqa: ARG001
precision: Optional[int],
verbosity: Optional[Verbosity], # noqa: ARG001
+ default: Any, # noqa: ARG001
) -> None:
"""
Set the value of an attribute from a subelement with a text node.
@@ -150,6 +154,7 @@ def coordinates_subelement(
node_name (str): The name of the subelement to create.
precision (Optional[int]): The precision of the attribute value.
verbosity (Optional[Verbosity]): The verbosity level.
+ default (Any): The default value of the attribute (unused).
Returns:
-------
@@ -157,15 +162,14 @@ def coordinates_subelement(
"""
if getattr(obj, attr_name, None):
- p = precision if precision is not None else 6
coords = getattr(obj, attr_name)
- if len(coords[0]) == 2: # noqa: PLR2004
- tuples = (f"{c[0]:.{p}f},{c[1]:.{p}f}" for c in coords)
- elif len(coords[0]) == 3: # noqa: PLR2004
- tuples = (f"{c[0]:.{p}f},{c[1]:.{p}f},{c[2]:.{p}f}" for c in coords)
- else:
+ if not coords or len(coords[0]) not in (2, 3):
msg = f"Invalid dimensions in coordinates '{coords}'"
raise KMLWriteError(msg)
+ if precision is None:
+ tuples = (",".join(str(c) for c in coord) for coord in coords)
+ else:
+ tuples = (",".join(f"{c:.{precision}f}" for c in coord) for coord in coords)
element.text = " ".join(tuples)
@@ -176,7 +180,7 @@ def subelement_coordinates_kwarg(
name_spaces: Dict[str, str], # noqa: ARG001
node_name: str, # noqa: ARG001
kwarg: str,
- classes: Tuple[known_types, ...], # noqa: ARG001
+ classes: Tuple[Type[object], ...], # noqa: ARG001
strict: bool,
) -> Dict[str, LineType]:
"""
@@ -189,7 +193,7 @@ def subelement_coordinates_kwarg(
name_spaces (Dict[str, str]): A dictionary mapping namespace prefixes to URIs.
node_name (str): The name of the XML node containing the coordinates.
kwarg (str): The name of the keyword argument to store the coordinates.
- classes (Tuple[known_types, ...]): A tuple of known types for validation.
+ classes (Tuple[Type[object], ...]): A tuple of known types for validation.
strict (bool): A flag indicating whether to raise an error for invalid geometry.
Returns:
@@ -318,8 +322,6 @@ class _Geometry(_BaseObject):
"""
- extrude: Optional[bool]
- tessellate: Optional[bool]
altitude_mode: Optional[AltitudeMode]
def __init__(
@@ -329,8 +331,6 @@ def __init__(
name_spaces: Optional[Dict[str, str]] = None,
id: Optional[str] = None,
target_id: Optional[str] = None,
- extrude: Optional[bool] = None,
- tessellate: Optional[bool] = None,
altitude_mode: Optional[AltitudeMode] = None,
**kwargs: Any,
) -> None:
@@ -357,8 +357,6 @@ def __init__(
target_id=target_id,
**kwargs,
)
- self.extrude = extrude
- self.tessellate = tessellate
self.altitude_mode = altitude_mode
def __repr__(self) -> str:
@@ -369,49 +367,12 @@ def __repr__(self) -> str:
f"name_spaces={self.name_spaces!r}, "
f"id={self.id!r}, "
f"target_id={self.target_id!r}, "
- f"extrude={self.extrude!r}, "
- f"tessellate={self.tessellate!r}, "
f"altitude_mode={self.altitude_mode}, "
f"**{self._get_splat()!r},"
")"
)
-registry.register(
- _Geometry,
- item=RegistryItem(
- ns_ids=("kml",),
- classes=(bool,),
- attr_name="extrude",
- node_name="extrude",
- get_kwarg=subelement_bool_kwarg,
- set_element=bool_subelement,
- ),
-)
-registry.register(
- _Geometry,
- item=RegistryItem(
- ns_ids=("kml",),
- classes=(bool,),
- attr_name="tessellate",
- node_name="tessellate",
- get_kwarg=subelement_bool_kwarg,
- set_element=bool_subelement,
- ),
-)
-registry.register(
- _Geometry,
- item=RegistryItem(
- ns_ids=("kml", "gx"),
- classes=(AltitudeMode,),
- attr_name="altitude_mode",
- node_name="altitudeMode",
- get_kwarg=subelement_enum_kwarg,
- set_element=enum_subelement,
- ),
-)
-
-
class Point(_Geometry):
"""
A geographic location defined by longitude, latitude, and (optional) altitude.
@@ -424,6 +385,7 @@ class Point(_Geometry):
https://developers.google.com/kml/documentation/kmlreference#point
"""
+ extrude: Optional[bool]
kml_coordinates: Optional[Coordinates]
def __init__(
@@ -434,7 +396,6 @@ def __init__(
id: Optional[str] = None,
target_id: Optional[str] = None,
extrude: Optional[bool] = None,
- tessellate: Optional[bool] = None,
altitude_mode: Optional[AltitudeMode] = None,
geometry: Optional[geo.Point] = None,
kml_coordinates: Optional[Coordinates] = None,
@@ -471,13 +432,13 @@ def __init__(
else None
)
self.kml_coordinates = kml_coordinates
+ self.extrude = extrude
+ kwargs.pop("tessellate", None)
super().__init__(
ns=ns,
id=id,
name_spaces=name_spaces,
target_id=target_id,
- extrude=extrude,
- tessellate=tessellate,
altitude_mode=altitude_mode,
**kwargs,
)
@@ -498,7 +459,6 @@ def __repr__(self) -> str:
f"id={self.id!r}, "
f"target_id={self.target_id!r}, "
f"extrude={self.extrude!r}, "
- f"tessellate={self.tessellate!r}, "
f"altitude_mode={self.altitude_mode}, "
f"kml_coordinates={self.kml_coordinates!r}, "
f"**{self._get_splat()!r},"
@@ -516,6 +476,21 @@ def __bool__(self) -> bool:
"""
return bool(self.geometry)
+ def __eq__(self, other: object) -> bool:
+ """Check if the Point objects are equal."""
+ if isinstance(other, Point):
+ return all(
+ getattr(self, attr) == getattr(other, attr)
+ for attr in (
+ "extrude",
+ "altitude_mode",
+ "geometry",
+ *xml_attrs,
+ *self._get_splat(),
+ )
+ )
+ return super().__eq__(other)
+
@property
def geometry(self) -> Optional[geo.Point]:
"""
@@ -535,6 +510,30 @@ def geometry(self) -> Optional[geo.Point]:
return None
+registry.register(
+ Point,
+ item=RegistryItem(
+ ns_ids=("kml",),
+ classes=(bool,),
+ attr_name="extrude",
+ node_name="extrude",
+ get_kwarg=subelement_bool_kwarg,
+ set_element=bool_subelement,
+ default=False,
+ ),
+)
+registry.register(
+ Point,
+ item=RegistryItem(
+ ns_ids=("kml", "gx"),
+ classes=(AltitudeMode,),
+ attr_name="altitude_mode",
+ node_name="altitudeMode",
+ get_kwarg=subelement_enum_kwarg,
+ set_element=enum_subelement,
+ default=AltitudeMode.clamp_to_ground,
+ ),
+)
registry.register(
Point,
item=RegistryItem(
@@ -562,6 +561,10 @@ class LineString(_Geometry):
https://developers.google.com/kml/documentation/kmlreference#linestring
"""
+ extrude: Optional[bool]
+ tessellate: Optional[bool]
+ kml_coordinates: Optional[Coordinates]
+
def __init__(
self,
*,
@@ -602,13 +605,13 @@ def __init__(
if kml_coordinates is None:
kml_coordinates = Coordinates(coords=geometry.coords) if geometry else None
self.kml_coordinates = kml_coordinates
+ self.extrude = extrude
+ self.tessellate = tessellate
super().__init__(
ns=ns,
name_spaces=name_spaces,
id=id,
target_id=target_id,
- extrude=extrude,
- tessellate=tessellate,
altitude_mode=altitude_mode,
**kwargs,
)
@@ -640,6 +643,21 @@ def __bool__(self) -> bool:
"""
return bool(self.geometry)
+ def __eq__(self, other: object) -> bool:
+ """Check if the LineString objects is equal."""
+ if isinstance(other, LineString):
+ return all(
+ getattr(self, attr) == getattr(other, attr)
+ for attr in (
+ "extrude",
+ "tessellate",
+ "geometry",
+ *xml_attrs,
+ *self._get_splat(),
+ )
+ )
+ return super().__eq__(other)
+
@property
def geometry(self) -> Optional[geo.LineString]:
"""
@@ -659,6 +677,42 @@ def geometry(self) -> Optional[geo.LineString]:
return None
+registry.register(
+ LineString,
+ item=RegistryItem(
+ ns_ids=("kml",),
+ classes=(bool,),
+ attr_name="extrude",
+ node_name="extrude",
+ get_kwarg=subelement_bool_kwarg,
+ set_element=bool_subelement,
+ default=False,
+ ),
+)
+registry.register(
+ LineString,
+ item=RegistryItem(
+ ns_ids=("kml",),
+ classes=(bool,),
+ attr_name="tessellate",
+ node_name="tessellate",
+ get_kwarg=subelement_bool_kwarg,
+ set_element=bool_subelement,
+ default=False,
+ ),
+)
+registry.register(
+ LineString,
+ item=RegistryItem(
+ ns_ids=("kml", "gx"),
+ classes=(AltitudeMode,),
+ attr_name="altitude_mode",
+ node_name="altitudeMode",
+ get_kwarg=subelement_enum_kwarg,
+ set_element=enum_subelement,
+ default=AltitudeMode.clamp_to_ground,
+ ),
+)
registry.register(
LineString,
item=RegistryItem(
@@ -741,22 +795,6 @@ def __init__(
**kwargs,
)
- def __repr__(self) -> str:
- """Create a string (c)representation for LinearRing."""
- return (
- f"{self.__class__.__module__}.{self.__class__.__name__}("
- f"ns={self.ns!r}, "
- f"name_spaces={self.name_spaces!r}, "
- f"id={self.id!r}, "
- f"target_id={self.target_id!r}, "
- f"extrude={self.extrude!r}, "
- f"tessellate={self.tessellate!r}, "
- f"altitude_mode={self.altitude_mode}, "
- f"geometry={self.geometry!r}, "
- f"**{self._get_splat()!r},"
- ")"
- )
-
@property
def geometry(self) -> Optional[geo.LinearRing]:
"""
@@ -779,9 +817,9 @@ def geometry(self) -> Optional[geo.LinearRing]:
return None
-class OuterBoundaryIs(_XMLObject):
+class BoundaryIs(_XMLObject):
"""
- Represents the outer boundary of a polygon in KML.
+ Represents the inner or outer boundary of a polygon in KML.
Attributes
----------
@@ -867,19 +905,6 @@ def __repr__(self) -> str:
")"
)
- @classmethod
- def get_tag_name(cls) -> str:
- """
- Get the tag name for the OuterBoundaryIs object.
-
- Returns
- -------
- str
- The tag name.
-
- """
- return "outerBoundaryIs"
-
@property
def geometry(self) -> Optional[geo.LinearRing]:
"""
@@ -894,106 +919,34 @@ def geometry(self) -> Optional[geo.LinearRing]:
return self.kml_geometry.geometry if self.kml_geometry else None
-registry.register(
- OuterBoundaryIs,
- item=RegistryItem(
- ns_ids=("kml", ""),
- classes=(LinearRing,),
- attr_name="kml_geometry",
- node_name="LinearRing",
- get_kwarg=xml_subelement_kwarg,
- set_element=xml_subelement,
- ),
-)
-
+class OuterBoundaryIs(BoundaryIs):
+ """Represents the outer boundary of a polygon in KML."""
-class InnerBoundaryIs(_XMLObject):
- """Represents the inner boundary of a polygon in KML."""
-
- _default_nsid = config.KML
- kml_geometry: Optional[LinearRing]
-
- def __init__(
- self,
- *,
- ns: Optional[str] = None,
- name_spaces: Optional[Dict[str, str]] = None,
- geometry: Optional[geo.LinearRing] = None,
- kml_geometry: Optional[LinearRing] = None,
- **kwargs: Any,
- ) -> None:
+ @classmethod
+ def get_tag_name(cls) -> str:
"""
- Initialize a Geometry object.
-
- Parameters
- ----------
- ns : Optional[str], optional
- The namespace for the KML element, by default None.
- name_spaces : Optional[Dict[str, str]], optional
- The namespace dictionary for the KML element, by default None.
- geometry : Optional[geo.LinearRing], optional
- The geometry to be converted to a KML geometry, by default None.
- kml_geometry : Optional[LinearRing], optional
- The KML geometry, by default None.
- **kwargs : Any
- Additional keyword arguments.
-
- Raises
- ------
- GeometryError
- If both `geometry` and `kml_geometry` are provided.
+ Get the tag name for the OuterBoundaryIs object.
- Notes
- -----
- - If `geometry` is provided, it will be converted to KML geometries and
- stored in `kml_geometry`.
- - If `geometry` and `kml_geometry` are both provided, a GeometryError will be
- raised.
+ Returns
+ -------
+ str
+ The tag name.
"""
- if geometry is not None and kml_geometry is not None:
- raise GeometryError(MsgMutualExclusive)
- if kml_geometry is None:
- kml_geometry = LinearRing(ns=ns, name_spaces=name_spaces, geometry=geometry)
- self.kml_geometry = kml_geometry
- super().__init__(
- ns=ns,
- name_spaces=name_spaces,
- **kwargs,
- )
+ return "outerBoundaryIs"
- def __bool__(self) -> bool:
- """Return True if any of the inner boundary geometries exist."""
- return bool(self.kml_geometry)
- def __repr__(self) -> str:
- """Create a string (c)representation for InnerBoundaryIs."""
- return (
- f"{self.__class__.__module__}.{self.__class__.__name__}("
- f"ns={self.ns!r}, "
- f"name_spaces={self.name_spaces!r}, "
- f"kml_geometry={self.kml_geometry!r}, "
- f"**{self._get_splat()},"
- ")"
- )
+class InnerBoundaryIs(BoundaryIs):
+ """Represents the inner boundary of a polygon in KML."""
@classmethod
def get_tag_name(cls) -> str:
"""Return the tag name of the element."""
return "innerBoundaryIs"
- @property
- def geometry(self) -> Optional[geo.LinearRing]:
- """
- Return the list of LinearRing objects representing the inner boundary.
-
- If no inner boundary geometries exist, returns None.
- """
- return self.kml_geometry.geometry if self.kml_geometry else None
-
registry.register(
- InnerBoundaryIs,
+ BoundaryIs,
item=RegistryItem(
ns_ids=("kml",),
classes=(LinearRing,),
@@ -1024,16 +977,18 @@ class Polygon(_Geometry):
The `geometry` property returns a `geo.Polygon` object representing the
geometry of the Polygon.
- Example usage:
- ```
- polygon = Polygon(outer_boundary_is=outer_boundary,
- inner_boundary_is=inner_boundary)
- print(polygon.geometry)
- ```
+ Example usage::
+
+ polygon = Polygon(outer_boundary_is=outer_boundary,
+ inner_boundary_is=inner_boundary)
+ print(polygon.geometry)
+
https://developers.google.com/kml/documentation/kmlreference#polygon
"""
+ extrude: Optional[bool]
+ tessellate: Optional[bool]
outer_boundary: Optional[OuterBoundaryIs]
inner_boundaries: List[InnerBoundaryIs]
@@ -1082,7 +1037,7 @@ def __init__(
Raises
------
- GeometryError
+ GeometryError:
If both outer_boundary_is and geometry are provided.
Returns
@@ -1099,13 +1054,13 @@ def __init__(
]
self.outer_boundary = outer_boundary
self.inner_boundaries = list(inner_boundaries) if inner_boundaries else []
+ self.extrude = extrude
+ self.tessellate = tessellate
super().__init__(
ns=ns,
name_spaces=name_spaces,
id=id,
target_id=target_id,
- extrude=extrude,
- tessellate=tessellate,
altitude_mode=altitude_mode,
**kwargs,
)
@@ -1167,13 +1122,63 @@ def __repr__(self) -> str:
f"extrude={self.extrude!r}, "
f"tessellate={self.tessellate!r}, "
f"altitude_mode={self.altitude_mode}, "
- f"outer_boundary={self.outer_boundary!r}, "
- f"inner_boundaries={self.inner_boundaries!r}, "
+ f"geometry={self.geometry!r}, "
f"**{self._get_splat()!r},"
")"
)
+ def __eq__(self, other: object) -> bool:
+ """Check if the Polygon objects are equal."""
+ if isinstance(other, Polygon):
+ return all(
+ getattr(self, attr) == getattr(other, attr)
+ for attr in (
+ "extrude",
+ "tessellate",
+ "geometry",
+ *xml_attrs,
+ *self._get_splat(),
+ )
+ )
+ return super().__eq__(other)
+
+registry.register(
+ Polygon,
+ item=RegistryItem(
+ ns_ids=("kml",),
+ classes=(bool,),
+ attr_name="extrude",
+ node_name="extrude",
+ get_kwarg=subelement_bool_kwarg,
+ set_element=bool_subelement,
+ default=False,
+ ),
+)
+registry.register(
+ Polygon,
+ item=RegistryItem(
+ ns_ids=("kml",),
+ classes=(bool,),
+ attr_name="tessellate",
+ node_name="tessellate",
+ get_kwarg=subelement_bool_kwarg,
+ set_element=bool_subelement,
+ default=False,
+ ),
+)
+registry.register(
+ Polygon,
+ item=RegistryItem(
+ ns_ids=("kml", "gx"),
+ classes=(AltitudeMode,),
+ attr_name="altitude_mode",
+ node_name="altitudeMode",
+ get_kwarg=subelement_enum_kwarg,
+ set_element=enum_subelement,
+ default=AltitudeMode.clamp_to_ground,
+ ),
+)
registry.register(
Polygon,
item=RegistryItem(
@@ -1232,69 +1237,7 @@ def create_multigeometry(
return geo.GeometryCollection(geometries)
-def create_kml_geometry(
- geometry: Union[GeoType, GeoCollectionType],
- *,
- ns: Optional[str] = None,
- name_spaces: Optional[Dict[str, str]] = None,
- id: Optional[str] = None,
- target_id: Optional[str] = None,
- extrude: Optional[bool] = None,
- tessellate: Optional[bool] = None,
- altitude_mode: Optional[AltitudeMode] = None,
-) -> _Geometry:
- """
- Create a KML geometry from a geometry object.
-
- Args:
- ----
- geometry: Geometry object.
- ns: Namespace of the object
- name_spaces: Name spaces of the object
- id: Id of the object
- target_id: Target id of the object
- extrude: Specifies whether to connect the feature to the ground with a line.
- tessellate: Specifies whether to allow the LineString to follow the terrain.
- altitude_mode: Specifies how altitude components in the
- element are interpreted.
-
- Returns:
- -------
- KML geometry object.
-
- """
- _map_to_kml = {
- geo.Point: Point,
- geo.Polygon: Polygon,
- geo.LinearRing: LinearRing,
- geo.LineString: LineString,
- geo.MultiPoint: MultiGeometry,
- geo.MultiLineString: MultiGeometry,
- geo.MultiPolygon: MultiGeometry,
- geo.GeometryCollection: MultiGeometry,
- }
- geom = shape(geometry)
- for geometry_class, kml_class in _map_to_kml.items():
- if isinstance(geom, geometry_class):
- return cast(
- _Geometry,
- kml_class(
- ns=ns,
- name_spaces=name_spaces,
- id=id,
- target_id=target_id,
- extrude=extrude,
- tessellate=tessellate,
- altitude_mode=altitude_mode,
- geometry=geom,
- ),
- )
- # this should be unreachable, but mypy doesn't know that
- msg = f"Unsupported geometry type {type(geometry)}" # pragma: no cover
- raise KMLWriteError(msg) # pragma: no cover
-
-
-class MultiGeometry(_Geometry):
+class MultiGeometry(_BaseObject):
"""A container for zero or more geometry primitives."""
kml_geometries: List[Union[Point, LineString, Polygon, LinearRing, Self]]
@@ -1328,17 +1271,26 @@ def __init__(
The ID of the KML element.
target_id : str, optional
The target ID of the KML element.
- extrude : bool, optional
- Specifies whether to extend the geometry to the ground.
- tessellate : bool, optional
- Specifies whether to allow the geometry to follow the terrain.
- altitude_mode : AltitudeMode, optional
- The altitude mode of the geometry.
kml_geometries : iterable of Point, LineString, Polygon, LinearRing,
MultiGeometry
A collection of KML geometries.
geometry : MultiGeometryType, optional
A multi-geometry object.
+ Parameters for geometry and kml_geometries are mutually exclusive.
+ When geometry is provided, kml_geometries will be created from it and
+ you can specify additional parameters like extrude, tessellate, and
+ altitude_mode which will be set on the individual geometries.
+ extrude : bool, optional
+ Specifies whether to extend the geometry to the ground.
+ This is not set on the multi-geometry itself, but on the individual
+ geometries.
+ tessellate : bool, optional
+ Specifies whether to allow the geometry to follow the terrain.
+ This is not set on the multi-geometry itself, but on the individual
+ geometries.
+ altitude_mode : AltitudeMode, optional
+ The altitude mode of the geometry. This is not set on the multi-geometry
+ itself, but on the individual geometries.
**kwargs : any
Additional keyword arguments.
@@ -1373,9 +1325,6 @@ def __init__(
name_spaces=name_spaces,
id=id,
target_id=target_id,
- extrude=extrude,
- tessellate=tessellate,
- altitude_mode=altitude_mode,
**kwargs,
)
@@ -1391,9 +1340,6 @@ def __repr__(self) -> str:
f"name_spaces={self.name_spaces!r}, "
f"id={self.id!r}, "
f"target_id={self.target_id!r}, "
- f"extrude={self.extrude!r}, "
- f"tessellate={self.tessellate!r}, "
- f"altitude_mode={self.altitude_mode}, "
f"kml_geometries={self.kml_geometries!r}, "
f"**{self._get_splat()!r},"
")"
@@ -1418,3 +1364,84 @@ def geometry(self) -> Optional[MultiGeometryType]:
set_element=xml_subelement_list,
),
)
+
+
+KMLGeometryType = Union[Point, LineString, Polygon, LinearRing, MultiGeometry]
+
+
+def _unknown_geometry_type(geometry: Union[GeoType, GeoCollectionType]) -> NoReturn:
+ """
+ Raise an error for an unknown geometry type.
+
+ Args:
+ ----
+ geometry: The geometry object.
+
+ Raises:
+ ------
+ KMLWriteError: If the geometry type is unknown.
+
+ """
+ msg = f"Unsupported geometry type {type(geometry)}" # pragma: no cover
+ raise KMLWriteError(msg) # pragma: no cover
+
+
+def create_kml_geometry(
+ geometry: Union[GeoType, GeoCollectionType],
+ *,
+ ns: Optional[str] = None,
+ name_spaces: Optional[Dict[str, str]] = None,
+ id: Optional[str] = None,
+ target_id: Optional[str] = None,
+ extrude: Optional[bool] = None,
+ tessellate: Optional[bool] = None,
+ altitude_mode: Optional[AltitudeMode] = None,
+) -> KMLGeometryType:
+ """
+ Create a KML geometry from a geometry object.
+
+ Args:
+ ----
+ geometry: Geometry object.
+ ns: Namespace of the object
+ name_spaces: Name spaces of the object
+ id: Id of the object
+ target_id: Target id of the object
+ extrude: Specifies whether to connect the feature to the ground with a line.
+ tessellate: Specifies whether to allow the LineString to follow the terrain.
+ altitude_mode: Specifies how altitude components in the
+ element are interpreted.
+
+ Returns:
+ -------
+ KML geometry object.
+
+ """
+ _map_to_kml: Dict[
+ Union[Type[GeoType], Type[GeoCollectionType]],
+ Type[KMLGeometryType],
+ ] = {
+ geo.Point: Point,
+ geo.Polygon: Polygon,
+ geo.LinearRing: LinearRing,
+ geo.LineString: LineString,
+ geo.MultiPoint: MultiGeometry,
+ geo.MultiLineString: MultiGeometry,
+ geo.MultiPolygon: MultiGeometry,
+ geo.GeometryCollection: MultiGeometry,
+ }
+ geom = shape(geometry)
+ for geometry_class, kml_class in _map_to_kml.items():
+ if isinstance(geom, geometry_class):
+ return kml_class(
+ ns=ns,
+ name_spaces=name_spaces,
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ geometry=geom, # type: ignore[arg-type]
+ )
+
+ _unknown_geometry_type(geometry) # pragma: no cover
diff --git a/fastkml/gx.py b/fastkml/gx.py
index 071e1321..736814a7 100644
--- a/fastkml/gx.py
+++ b/fastkml/gx.py
@@ -77,39 +77,42 @@
The complete XML schema for elements in this extension namespace is
located at http://developers.google.com/kml/schema/kml22gx.xsd.
"""
-import datetime
import logging
from dataclasses import dataclass
from itertools import zip_longest
from typing import Any
from typing import Dict
from typing import Iterable
-from typing import Iterator
from typing import List
from typing import Optional
+from typing import Tuple
+from typing import cast
-import arrow
import pygeoif.geometry as geo
+from pygeoif.types import PointType
from fastkml import config
from fastkml.enums import AltitudeMode
-from fastkml.enums import Verbosity
from fastkml.geometry import _Geometry
from fastkml.helpers import bool_subelement
+from fastkml.helpers import coords_subelement_list
+from fastkml.helpers import coords_subelement_list_kwarg
+from fastkml.helpers import datetime_subelement_list
+from fastkml.helpers import datetime_subelement_list_kwarg
+from fastkml.helpers import enum_subelement
from fastkml.helpers import subelement_bool_kwarg
+from fastkml.helpers import subelement_enum_kwarg
from fastkml.helpers import xml_subelement_list
from fastkml.helpers import xml_subelement_list_kwarg
from fastkml.registry import RegistryItem
from fastkml.registry import registry
-from fastkml.types import Element
+from fastkml.times import KmlDateTime
__all__ = [
"Angle",
"MultiTrack",
"Track",
"TrackItem",
- "linestring_to_track_items",
- "multilinestring_to_tracks",
"track_items_to_geometry",
"tracks_to_geometry",
]
@@ -131,62 +134,27 @@ class Angle:
tilt: float = 0.0
roll: float = 0.0
+ @property
+ def coords(self) -> PointType:
+ """
+ Get the coordinates of the angle.
-@dataclass(frozen=True)
-class TrackItem:
- """A track item describes an objects position and heading at a specific time."""
-
- when: Optional[datetime.datetime] = None
- coord: Optional[geo.Point] = None
- angle: Optional[Angle] = None
+ Returns
+ -------
+ PointType
+ The coordinates of the angle.
- def etree_elements(
- self,
- *,
- precision: Optional[int] = None, # noqa: ARG002
- verbosity: Verbosity = Verbosity.normal, # noqa: ARG002
- name_spaces: Optional[Dict[str, str]] = None,
- ) -> Iterator[Element]:
"""
- Generate XML elements for the gx:when, gx:coord, and gx:angles elements.
+ return (self.heading, self.tilt, self.roll)
- Args:
- ----
- precision (Optional[int]): The precision of the coordinates.
- verbosity (Verbosity): The verbosity level. Defaults to Verbosity.normal.
- name_spaces (Optional[Dict[str, str]]): Additional XML namespaces.
- Yields:
- ------
- Element: The generated XML elements.
+@dataclass(frozen=True)
+class TrackItem:
+ """A track item describes an objects position and heading at a specific time."""
- """
- name_spaces = name_spaces or {}
- name_spaces = {**config.NAME_SPACES, **name_spaces}
- element: Element = config.etree.Element(
- f"{name_spaces.get('kml', '')}when",
- )
- if self.when:
- element.text = self.when.isoformat()
- yield element
- element = config.etree.Element(
- f"{name_spaces.get('gx', '')}coord",
- )
- if self.coord:
- element.text = " ".join(
- [
- str(c) for c in self.coord.coords[0] # type:ignore[misc]
- ],
- )
- yield element
- element = config.etree.Element(
- f"{name_spaces.get('gx', '')}angles",
- )
- if self.angle:
- element.text = " ".join(
- [str(self.angle.heading), str(self.angle.tilt), str(self.angle.roll)],
- )
- yield element
+ when: KmlDateTime
+ coord: geo.Point
+ angle: Optional[Angle] = None
def track_items_to_geometry(track_items: Iterable[TrackItem]) -> geo.LineString:
@@ -209,22 +177,6 @@ def track_items_to_geometry(track_items: Iterable[TrackItem]) -> geo.LineString:
)
-def linestring_to_track_items(linestring: geo.LineString) -> List[TrackItem]:
- """
- Convert a LineString to a list of TrackItems.
-
- Args:
- ----
- linestring (LineString): The LineString to convert.
-
- Returns:
- -------
- List[TrackItem]: A list of TrackItems representing the points in the LineString.
-
- """
- return [TrackItem(coord=point) for point in linestring.geoms]
-
-
class Track(_Geometry):
"""
A track describes how an object moves through the world over a given time period.
@@ -237,10 +189,11 @@ class Track(_Geometry):
Tracks are a more efficient mechanism for associating time data with visible
Features, since you create only one Feature, which can be associated with multiple
time elements as the object moves through space.
+
+ https://developers.google.com/kml/documentation/kmlreference#gxtrack
"""
_default_nsid = config.GX
-
track_items: List[TrackItem]
def __init__(
@@ -250,11 +203,11 @@ def __init__(
name_spaces: Optional[Dict[str, str]] = None,
id: Optional[str] = None,
target_id: Optional[str] = None,
- extrude: Optional[bool] = None,
- tessellate: Optional[bool] = None,
altitude_mode: Optional[AltitudeMode] = None,
- geometry: Optional[geo.LineString] = None,
track_items: Optional[Iterable[TrackItem]] = None,
+ whens: Optional[Iterable[KmlDateTime]] = None,
+ coords: Optional[Iterable[PointType]] = None,
+ angles: Optional[Iterable[PointType]] = None,
**kwargs: Any,
) -> None:
"""
@@ -270,16 +223,16 @@ def __init__(
The ID of the GX object, by default None
target_id : Optional[str], optional
The target ID of the GX object, by default None
- extrude : Optional[bool], optional
- Whether to extrude the GX object, by default None
- tessellate : Optional[bool], optional
- Whether to tessellate the GX object, by default None
altitude_mode : Optional[AltitudeMode], optional
The altitude mode of the GX object, by default None
- geometry : Optional[geo.LineString], optional
- The geometry of the GX object, by default None
track_items : Optional[Iterable[TrackItem]], optional
The track items of the GX object, by default None
+ whens : Optional[Iterable[KmlDateTime]], optional
+ The timestamps of the track items, by default None
+ coords : Optional[Iterable[PointType]], optional
+ The coordinates of the track items, by default None
+ angles : Optional[Iterable[PointType]], optional
+ The angles of the track items, by default None
**kwargs : Any, optional
Additional keyword arguments.
@@ -289,19 +242,30 @@ def __init__(
If both `geometry` and `track_items` are specified.
"""
- if geometry and track_items:
+ angles = list(angles) if angles else []
+ if (whens or coords) and track_items:
msg = "Cannot specify both geometry and track_items"
raise ValueError(msg)
- if geometry:
- track_items = linestring_to_track_items(geometry)
+ if not track_items and whens and coords:
+ track_items = [
+ TrackItem(
+ when=cast(KmlDateTime, when),
+ coord=geo.Point(*coord),
+ angle=Angle(*angle),
+ )
+ for when, coord, angle in zip_longest(
+ whens,
+ coords,
+ angles,
+ fillvalue=(),
+ )
+ ]
self.track_items = list(track_items) if track_items else []
super().__init__(
ns=ns,
name_spaces=name_spaces,
id=id,
target_id=target_id,
- extrude=extrude,
- tessellate=tessellate,
altitude_mode=altitude_mode,
**kwargs,
)
@@ -322,10 +286,7 @@ def __repr__(self) -> str:
f"name_spaces={self.name_spaces!r}, "
f"id={self.id!r}, "
f"target_id={self.target_id!r}, "
- f"extrude={self.extrude!r}, "
- f"tessellate={self.tessellate!r}, "
f"altitude_mode={self.altitude_mode}, "
- f"geometry={self.geometry!r}, "
f"track_items={self.track_items!r}, "
f"**{self._get_splat()!r},"
")"
@@ -344,225 +305,108 @@ def geometry(self) -> Optional[geo.LineString]:
"""
return track_items_to_geometry(self.track_items)
- def __bool__(self) -> bool:
- """
- Check if the track has any track items.
-
- Returns
- -------
- bool
- True if the track has track items, False otherwise.
-
- """
- return bool(self.track_items)
-
- def etree_element(
- self,
- precision: Optional[int] = None,
- verbosity: Verbosity = Verbosity.normal,
- name_spaces: Optional[Dict[str, str]] = None,
- ) -> Element:
+ @property
+ def whens(self) -> Tuple[KmlDateTime, ...]:
"""
- Get the ElementTree element representation of the track.
-
- Parameters
- ----------
- precision : Optional[int], optional
- The precision for floating-point values, by default None
- verbosity : Verbosity, optional
- The verbosity level for the element, by default Verbosity.normal
- name_spaces : Optional[Dict[str, str]], optional
- A dictionary of namespace prefixes and URIs, by default None
+ Get the timestamps of the track items.
Returns
-------
- Element
- The ElementTree element representation of the track.
+ Tuple[KmlDateTime]
+ The timestamps of the track items.
"""
- element = super().etree_element(precision=precision, verbosity=verbosity)
- if self.track_items:
- for track_item in self.track_items:
- for track_item_element in track_item.etree_elements(
- precision=precision,
- verbosity=verbosity,
- name_spaces=name_spaces,
- ):
- element.append(track_item_element)
- return element
-
- @classmethod
- def _get_timestamps(cls, element: Element) -> List[Optional[datetime.datetime]]:
- """
- Get the timestamps from the XML element.
+ return tuple(item.when for item in self.track_items)
- Parameters
- ----------
- element : Element
- The XML element.
-
- Returns
- -------
- List[Optional[datetime.datetime]]
- The list of timestamps.
-
- """
- time_stamps: List[Optional[datetime.datetime]] = []
- for time_stamp in element.findall(f"{config.KMLNS}when"):
- if time_stamp is not None and time_stamp.text:
- time_stamps.append(arrow.get(time_stamp.text).datetime)
- else:
- time_stamps.append(None)
- return time_stamps
-
- @classmethod
- def _get_coords(cls, element: Element) -> List[Optional[geo.Point]]:
+ @property
+ def coords(self) -> Tuple[PointType, ...]:
"""
- Get the coordinates from the XML element.
-
- Parameters
- ----------
- element : Element
- The XML element.
+ Get the coordinates of the track items.
Returns
-------
- List[Optional[geo.Point]]
- The list of coordinates.
+ Tuple[PointType]
+ The coordinates of the track items.
"""
- coords: List[Optional[geo.Point]] = []
- for coord in element.findall(f"{config.GXNS}coord"):
- if coord is not None and coord.text:
- coords.append(
- geo.Point(*[float(c) for c in coord.text.strip().split()]),
- )
- else:
- coords.append(None)
- return coords
+ return tuple(
+ item.coord.coords[0] # type: ignore[misc]
+ for item in self.track_items
+ if item.coord
+ )
- @classmethod
- def _get_angles(cls, element: Element) -> List[Optional[Angle]]:
+ @property
+ def angles(self) -> Tuple[PointType, ...]:
"""
- Get the angles from the XML element.
-
- Parameters
- ----------
- element : Element
- The XML element.
+ Get the angles of the track items.
Returns
-------
- List[Optional[Angle]]
- The list of angles.
+ Tuple[Angle]
+ The angles of the track items.
"""
- angles: List[Optional[Angle]] = []
- for angle in element.findall(f"{config.GXNS}angles"):
- if angle is not None and angle.text:
- angles.append(Angle(*[float(a) for a in angle.text.strip().split()]))
- else:
- angles.append(None)
- return angles
-
- @classmethod
- def track_items_kwargs_from_element(
- cls,
- *,
- ns: str, # noqa: ARG003
- element: Element,
- strict: bool, # noqa: ARG003
- ) -> List[TrackItem]:
- """
- Get the track item keyword arguments from the XML element.
-
- Parameters
- ----------
- ns : str
- The namespace for the GX object.
- element : Element
- The XML element.
- strict : bool
- Whether to enforce strict parsing.
-
- Returns
- -------
- List[TrackItem]
- The list of track items.
+ return tuple(item.angle.coords for item in self.track_items if item.angle)
+ def __bool__(self) -> bool:
"""
- time_stamps = cls._get_timestamps(element)
- coords = cls._get_coords(element)
- angles = cls._get_angles(element)
- return [
- TrackItem(when=when, coord=coord, angle=angle)
- for when, coord, angle in zip_longest(time_stamps, coords, angles)
- ]
-
- @classmethod
- def _get_kwargs(
- cls,
- *,
- ns: str,
- name_spaces: Optional[Dict[str, str]] = None,
- element: Element,
- strict: bool,
- ) -> Dict[str, Any]:
- """
- Get the keyword arguments for the track.
-
- Parameters
- ----------
- ns : str
- The namespace for the GX object.
- name_spaces : Optional[Dict[str, str]], optional
- A dictionary of namespace prefixes and URIs, by default None
- element : Element
- The XML element.
- strict : bool
- Whether to enforce strict parsing.
+ Check if the track has any track items.
Returns
-------
- Dict[str, Any]
- The keyword arguments for the track.
+ bool
+ True if the track has track items, False otherwise.
"""
- kwargs = super()._get_kwargs(
- ns=ns,
- name_spaces=name_spaces,
- element=element,
- strict=strict,
- )
- kwargs["track_items"] = cls.track_items_kwargs_from_element(
- ns=ns,
- element=element,
- strict=strict,
- )
- return kwargs
-
-
-def multilinestring_to_tracks(
- multilinestring: geo.MultiLineString,
- ns: Optional[str],
-) -> List[Track]:
- """
- Convert a MultiLineString to a list of Track objects.
-
- Args:
- ----
- multilinestring : geo.MultiLineString:
- The MultiLineString to convert.
- ns : str, optional:
- The namespace for the Track objects.
+ return bool(self.track_items)
- Returns:
- -------
- List[Track]:
- A list of Track objects.
- """
- return [Track(ns=ns, geometry=linestring) for linestring in multilinestring.geoms]
+registry.register(
+ Track,
+ item=RegistryItem(
+ ns_ids=("gx", "kml", ""),
+ classes=(AltitudeMode,),
+ attr_name="altitude_mode",
+ node_name="altitudeMode",
+ get_kwarg=subelement_enum_kwarg,
+ set_element=enum_subelement,
+ default=AltitudeMode.clamp_to_ground,
+ ),
+)
+registry.register(
+ Track,
+ item=RegistryItem(
+ ns_ids=("kml", "gx", ""),
+ classes=(KmlDateTime,),
+ attr_name="whens",
+ node_name="when",
+ get_kwarg=datetime_subelement_list_kwarg,
+ set_element=datetime_subelement_list,
+ ),
+)
+registry.register(
+ Track,
+ item=RegistryItem(
+ ns_ids=("gx", ""),
+ classes=(tuple,),
+ attr_name="coords",
+ node_name="coord",
+ get_kwarg=coords_subelement_list_kwarg,
+ set_element=coords_subelement_list,
+ ),
+)
+registry.register(
+ Track,
+ item=RegistryItem(
+ ns_ids=("gx", ""),
+ classes=(tuple,),
+ attr_name="angles",
+ node_name="angles",
+ get_kwarg=coords_subelement_list_kwarg,
+ set_element=coords_subelement_list,
+ default=(0.0, 0.0, 0.0),
+ ),
+)
def tracks_to_geometry(tracks: Iterable[Track]) -> geo.MultiLineString:
@@ -613,10 +457,7 @@ def __init__(
name_spaces: Optional[Dict[str, str]] = None,
id: Optional[str] = None,
target_id: Optional[str] = None,
- extrude: Optional[bool] = None,
- tessellate: Optional[bool] = None,
altitude_mode: Optional[AltitudeMode] = None,
- geometry: Optional[geo.MultiLineString] = None,
tracks: Optional[Iterable[Track]] = None,
interpolate: Optional[bool] = None,
**kwargs: Any,
@@ -631,8 +472,6 @@ def __init__(
and URIs.
id (Optional[str]): The ID of the GX object.
target_id (Optional[str]): The target ID of the GX object.
- extrude (Optional[bool]): The extrude flag of the GX object.
- tessellate (Optional[bool]): The tessellate flag of the GX object.
altitude_mode (Optional[AltitudeMode]): The altitude mode of the GX object.
geometry (Optional[geo.MultiLineString]): The geometry of the GX object.
tracks (Optional[Iterable[Track]]): The tracks of the GX object.
@@ -644,20 +483,13 @@ def __init__(
ValueError: If both geometry and tracks are specified.
"""
- if geometry and tracks:
- msg = "Cannot specify both geometry and track_items"
- raise ValueError(msg)
- if geometry:
- tracks = multilinestring_to_tracks(geometry, ns=ns)
- self.tracks = list(tracks) if tracks else []
+ self.tracks = [t for t in tracks if t] if tracks else []
self.interpolate = interpolate
super().__init__(
ns=ns,
name_spaces=name_spaces,
id=id,
target_id=target_id,
- extrude=extrude,
- tessellate=tessellate,
altitude_mode=altitude_mode,
**kwargs,
)
@@ -670,10 +502,7 @@ def __repr__(self) -> str:
f"name_spaces={self.name_spaces!r}, "
f"id={self.id!r}, "
f"target_id={self.target_id!r}, "
- f"extrude={self.extrude!r}, "
- f"tessellate={self.tessellate!r}, "
f"altitude_mode={self.altitude_mode}, "
- f"geometry={self.geometry!r}, "
f"tracks={self.tracks!r}, "
f"interpolate={self.interpolate!r}, "
f"**{self._get_splat()!r},"
@@ -705,6 +534,18 @@ def __bool__(self) -> bool:
return bool(self.tracks)
+registry.register(
+ MultiTrack,
+ item=RegistryItem(
+ ns_ids=("gx", "kml"),
+ classes=(AltitudeMode,),
+ attr_name="altitude_mode",
+ node_name="altitudeMode",
+ get_kwarg=subelement_enum_kwarg,
+ set_element=enum_subelement,
+ default=AltitudeMode.clamp_to_ground,
+ ),
+)
registry.register(
MultiTrack,
item=RegistryItem(
diff --git a/fastkml/helpers.py b/fastkml/helpers.py
index 193365ec..51d8791e 100644
--- a/fastkml/helpers.py
+++ b/fastkml/helpers.py
@@ -17,19 +17,27 @@
import logging
from enum import Enum
+from typing import TYPE_CHECKING
+from typing import Any
from typing import Dict
from typing import List
from typing import Optional
from typing import Tuple
from typing import Type
+from typing import cast
+
+from pygeoif.types import PointType
from fastkml import config
-from fastkml.base import _XMLObject
from fastkml.enums import Verbosity
from fastkml.exceptions import KMLParseError
-from fastkml.registry import known_types
from fastkml.types import Element
+if TYPE_CHECKING:
+ from fastkml.base import _XMLObject
+ from fastkml.times import KmlDateTime
+
+
logger = logging.getLogger(__name__)
@@ -79,21 +87,59 @@ def handle_error(
logger.warning("%s, %s", error, msg)
+def get_ns(obj: "_XMLObject", value: object) -> str:
+ """Get the namespace of an attribute, fall back on the objects namespace."""
+ try:
+ return obj.name_spaces.get(value.get_ns_id(), "") # type: ignore[attr-defined]
+ except AttributeError:
+ return obj.ns
+
+
+def get_value(
+ obj: "_XMLObject",
+ *,
+ attr_name: str,
+ verbosity: Verbosity,
+ default: Optional[Any],
+) -> Optional[Any]:
+ """
+ Get the value of an attribute from an object.
+
+ If the verbosity is set to `Verbosity.terse`, the function returns `None` if the
+ attribute value is equal to the default value. If the verbosity is set to
+ `Verbosity.verbose`, the function returns the default value if the attribute value
+ is `None`.
+
+ Args:
+ ----
+ obj ("_XMLObject"): The object to get the attribute value from.
+ attr_name (str): The name of the attribute to retrieve.
+ verbosity (Optional[Verbosity]): The verbosity.
+ default (Optional[Any]): The default value.
+
+ """
+ value = getattr(obj, attr_name, None)
+ if value is None and default is not None and verbosity == Verbosity.verbose:
+ return default
+ return None if value == default and verbosity == Verbosity.terse else value
+
+
def node_text(
- obj: _XMLObject,
+ obj: "_XMLObject",
*,
element: Element,
attr_name: str,
node_name: str,
precision: Optional[int],
- verbosity: Optional[Verbosity],
+ verbosity: Verbosity,
+ default: Optional[str],
) -> None:
"""
Set the text of an XML element based on the attribute value in the given object.
Parameters
----------
- obj : _XMLObject
+ obj : "_XMLObject"
The object containing the attribute value.
element : Element
The XML element to set the text content for.
@@ -105,6 +151,8 @@ def node_text(
The precision to use when converting numeric values to text (unused).
verbosity : Optional[Verbosity]
The verbosity level for logging (unused).
+ default : Optional[str]
+ The default value for the attribute.
Returns
-------
@@ -112,256 +160,375 @@ def node_text(
This function does not return anything.
"""
- if getattr(obj, attr_name, None):
- element.text = getattr(obj, attr_name)
+ if value := get_value(
+ obj,
+ attr_name=attr_name,
+ verbosity=verbosity,
+ default=default,
+ ):
+ element.text = value
def text_subelement(
- obj: _XMLObject,
+ obj: "_XMLObject",
*,
element: Element,
attr_name: str,
node_name: str,
precision: Optional[int],
- verbosity: Optional[Verbosity],
+ verbosity: Verbosity,
+ default: Optional[str],
) -> None:
"""
Set the value of an attribute from a subelement with a text node.
Args:
----
- obj (_XMLObject): The object from which to retrieve the attribute value.
+ obj ("_XMLObject"): The object from which to retrieve the attribute value.
element (Element): The parent element to add the subelement to.
attr_name (str): The name of the attribute to retrieve the value from.
node_name (str): The name of the subelement to create.
precision (Optional[int]): The precision of the attribute value.
verbosity (Optional[Verbosity]): The verbosity level.
+ default (Optional[str]): The default value for the attribute.
Returns:
-------
None
"""
- if getattr(obj, attr_name, None):
+ if value := get_value(
+ obj,
+ attr_name=attr_name,
+ verbosity=verbosity,
+ default=default,
+ ):
subelement = config.etree.SubElement(
element,
f"{obj.ns}{node_name}",
)
- subelement.text = getattr(obj, attr_name)
+ subelement.text = value
def text_attribute(
- obj: _XMLObject,
+ obj: "_XMLObject",
*,
element: Element,
attr_name: str,
node_name: str,
precision: Optional[int],
- verbosity: Optional[Verbosity],
+ verbosity: Verbosity,
+ default: Optional[str],
) -> None:
"""
Set the value of an attribute from a subelement with a text node.
Args:
----
- obj (_XMLObject): The object from which to retrieve the attribute value.
+ obj ("_XMLObject"): The object from which to retrieve the attribute value.
element (Element): The parent element to add the subelement to.
attr_name (str): The name of the attribute to retrieve the value from.
node_name (str): The name of the attribute to be set.
precision (Optional[int]): The precision of the attribute value.
verbosity (Optional[Verbosity]): The verbosity level.
+ default (Optional[str]): The default value for the attribute.
Returns:
-------
None
"""
- if getattr(obj, attr_name, None):
- element.set(node_name, getattr(obj, attr_name))
+ if value := get_value(
+ obj,
+ attr_name=attr_name,
+ verbosity=verbosity,
+ default=default,
+ ):
+ element.set(node_name, value)
def bool_subelement(
- obj: _XMLObject,
+ obj: "_XMLObject",
*,
element: Element,
attr_name: str,
node_name: str,
precision: Optional[int],
- verbosity: Optional[Verbosity],
+ verbosity: Verbosity,
+ default: Optional[bool],
) -> None:
"""
Set the value of an attribute from a subelement with a text node.
Args:
----
- obj (_XMLObject): The object from which to retrieve the attribute value.
+ obj ("_XMLObject"): The object from which to retrieve the attribute value.
element (Element): The parent element to add the subelement to.
attr_name (str): The name of the attribute to retrieve the value from.
node_name (str): The name of the subelement to create.
precision (Optional[int]): The precision of the attribute value.
verbosity (Optional[Verbosity]): The verbosity level.
+ default (Optional[bool]): The default value for the attribute.
Returns:
-------
None
"""
- if getattr(obj, attr_name, None) is not None:
+ value = get_value(obj, attr_name=attr_name, verbosity=verbosity, default=default)
+ if value is not None:
subelement = config.etree.SubElement(
element,
f"{obj.ns}{node_name}",
)
- subelement.text = str(int(getattr(obj, attr_name)))
+ subelement.text = str(int(value))
def int_subelement(
- obj: _XMLObject,
+ obj: "_XMLObject",
*,
element: Element,
attr_name: str,
node_name: str,
precision: Optional[int],
- verbosity: Optional[Verbosity],
+ verbosity: Verbosity,
+ default: Optional[int],
) -> None:
"""
Set the value of an attribute from a subelement with a text node.
Args:
----
- obj (_XMLObject): The object from which to retrieve the attribute value.
+ obj ("_XMLObject"): The object from which to retrieve the attribute value.
element (Element): The parent element to add the subelement to.
attr_name (str): The name of the attribute to retrieve the value from.
node_name (str): The name of the subelement to create.
precision (Optional[int]): The precision of the attribute value.
verbosity (Optional[Verbosity]): The verbosity level.
+ default (Optional[int]): The default value for the attribute.
Returns:
-------
None: This function does not return anything.
"""
- if getattr(obj, attr_name, None) is not None:
+ value = get_value(obj, attr_name=attr_name, verbosity=verbosity, default=default)
+ if value is not None:
subelement = config.etree.SubElement(
element,
f"{obj.ns}{node_name}",
)
- subelement.text = str(getattr(obj, attr_name))
+ subelement.text = str(value)
def int_attribute(
- obj: _XMLObject,
+ obj: "_XMLObject",
*,
element: Element,
attr_name: str,
node_name: str,
precision: Optional[int],
- verbosity: Optional[Verbosity],
+ verbosity: Verbosity,
+ default: Optional[int],
) -> None:
"""
Set the value of an attribute.
Args:
----
- obj (_XMLObject): The object from which to retrieve the attribute value.
+ obj ("_XMLObject"): The object from which to retrieve the attribute value.
element (Element): The parent element to add the subelement to.
attr_name (str): The name of the attribute to retrieve the value from.
node_name (str): The name of the attribute to be set.
precision (Optional[int]): The precision of the attribute value.
verbosity (Optional[Verbosity]): The verbosity level.
+ default (Optional[int]): The default value for the attribute.
Returns:
-------
None: This function does not return anything.
"""
- if getattr(obj, attr_name, None) is not None:
- element.set(node_name, str(getattr(obj, attr_name)))
+ value = get_value(obj, attr_name=attr_name, verbosity=verbosity, default=default)
+ if value is not None:
+ element.set(node_name, str(value))
def float_subelement(
- obj: _XMLObject,
+ obj: "_XMLObject",
*,
element: Element,
attr_name: str,
node_name: str,
precision: Optional[int],
- verbosity: Optional[Verbosity],
+ verbosity: Verbosity,
+ default: Optional[float],
) -> None:
"""Set the value of an attribute from a subelement with a text node."""
- if getattr(obj, attr_name, None) is not None:
+ value = get_value(obj, attr_name=attr_name, verbosity=verbosity, default=default)
+ if value is not None:
subelement = config.etree.SubElement(
element,
f"{obj.ns}{node_name}",
)
- subelement.text = str(getattr(obj, attr_name))
+ subelement.text = str(value)
def float_attribute(
- obj: _XMLObject,
+ obj: "_XMLObject",
*,
element: Element,
attr_name: str,
node_name: str,
precision: Optional[int],
- verbosity: Optional[Verbosity],
+ verbosity: Verbosity,
+ default: Optional[float],
) -> None:
"""Set the value of an attribute."""
- if getattr(obj, attr_name, None) is not None:
- element.set(node_name, str(getattr(obj, attr_name)))
+ value = get_value(obj, attr_name=attr_name, verbosity=verbosity, default=default)
+ if value is not None:
+ element.set(node_name, str(value))
def enum_subelement(
- obj: _XMLObject,
+ obj: "_XMLObject",
*,
element: Element,
attr_name: str,
node_name: str,
precision: Optional[int],
- verbosity: Optional[Verbosity],
+ verbosity: Verbosity,
+ default: Optional[Enum],
) -> None:
"""Set the value of an attribute from a subelement with a text node."""
- if getattr(obj, attr_name, None):
+ value = get_value(obj, attr_name=attr_name, verbosity=verbosity, default=default)
+ if value is not None:
+ ns = get_ns(obj, value)
subelement = config.etree.SubElement(
element,
- f"{obj.ns}{node_name}",
+ f"{ns or ''}{node_name}",
)
- subelement.text = getattr(obj, attr_name).value
+ subelement.text = value.value
def enum_attribute(
- obj: _XMLObject,
+ obj: "_XMLObject",
*,
element: Element,
attr_name: str,
node_name: str,
precision: Optional[int],
- verbosity: Optional[Verbosity],
+ verbosity: Verbosity,
+ default: Optional[Enum],
) -> None:
"""Set the value of an attribute."""
- if getattr(obj, attr_name, None):
- element.set(node_name, getattr(obj, attr_name).value)
+ value = get_value(obj, attr_name=attr_name, verbosity=verbosity, default=default)
+ if value is not None:
+ element.set(node_name, value.value)
+
+
+def datetime_subelement(
+ obj: "_XMLObject",
+ *,
+ element: Element,
+ attr_name: str,
+ node_name: str,
+ precision: Optional[int],
+ verbosity: Verbosity,
+ default: Optional[str],
+) -> None:
+ """Create the subelement for a KML datetime values."""
+ if value := get_value(
+ obj,
+ attr_name=attr_name,
+ verbosity=verbosity,
+ default=default,
+ ):
+ ns = get_ns(obj, value)
+ subelement = config.etree.SubElement(
+ element,
+ f"{ns}{node_name}",
+ )
+ subelement.text = str(value)
+
+
+def datetime_subelement_list(
+ obj: "_XMLObject",
+ *,
+ element: Element,
+ attr_name: str,
+ node_name: str,
+ precision: Optional[int],
+ verbosity: Verbosity,
+ default: Optional[str],
+) -> None:
+ """Create the subelements for a list of KML datetime values."""
+ if value := get_value(
+ obj,
+ attr_name=attr_name,
+ verbosity=verbosity,
+ default=default,
+ ):
+ for item in value:
+ ns = get_ns(obj, item)
+ subelement = config.etree.SubElement(
+ element,
+ f"{ns}{node_name}",
+ )
+ subelement.text = str(item)
+
+
+def coords_subelement_list(
+ obj: "_XMLObject",
+ *,
+ element: Element,
+ attr_name: str,
+ node_name: str,
+ precision: Optional[int],
+ verbosity: Verbosity,
+ default: Optional[str],
+) -> None:
+ """Create the subelements for a list of KML coordinate values."""
+ if value := get_value(
+ obj,
+ attr_name=attr_name,
+ verbosity=verbosity,
+ default=default,
+ ):
+ for coord in value:
+ ns = get_ns(obj, coord)
+ subelement = config.etree.SubElement(
+ element,
+ f"{ns}{node_name}",
+ )
+ if precision is None:
+ subelement.text = " ".join(str(c) for c in coord)
+ else:
+ subelement.text = " ".join(f"{c:.{precision}f}" for c in coord)
def xml_subelement(
- obj: _XMLObject,
+ obj: "_XMLObject",
*,
element: Element,
attr_name: str,
node_name: str,
precision: Optional[int],
- verbosity: Optional[Verbosity],
+ verbosity: Verbosity,
+ default: Optional["_XMLObject"],
) -> None:
"""
Add a subelement to an XML element based on the value of an attribute of an object.
Args:
----
- obj (_XMLObject): The object containing the attribute.
+ obj ("_XMLObject"): The object containing the attribute.
element (Element): The XML element to which the subelement will be added.
attr_name (str): The name of the attribute in the object.
node_name (str): The name of the XML node for the subelement (unused).
precision (Optional[int]): The precision for formatting numerical values.
verbosity (Optional[Verbosity]): The verbosity level for the subelement.
+ default (Optional["_XMLObject"]): The default value for the attribute (unused).
Returns:
-------
@@ -378,25 +545,27 @@ def xml_subelement(
def xml_subelement_list(
- obj: _XMLObject,
+ obj: "_XMLObject",
*,
element: Element,
attr_name: str,
node_name: str,
precision: Optional[int],
- verbosity: Optional[Verbosity],
+ verbosity: Verbosity,
+ default: Optional[List["_XMLObject"]],
) -> None:
"""
Add subelements to an XML element based on a list attribute of an object.
Args:
----
- obj (_XMLObject): The object containing the list attribute.
+ obj ("_XMLObject"): The object containing the list attribute.
element (Element): The XML element to which the subelements will be added.
attr_name (str): The name of the list attribute in the object.
node_name (str): The name of the XML node for each subelement (unused).
precision (Optional[int]): The precision for floating-point values.
verbosity (Optional[Verbosity]): The verbosity level for the XML output.
+ default (Optional[List["_XMLObject"]]): The default value for the attribute.
Returns:
-------
@@ -418,7 +587,7 @@ def node_text_kwarg(
name_spaces: Dict[str, str],
node_name: str,
kwarg: str,
- classes: Tuple[known_types, ...],
+ classes: Tuple[Type[object], ...],
strict: bool,
) -> Dict[str, str]:
"""
@@ -428,11 +597,11 @@ def node_text_kwarg(
----
element (Element): The XML element to extract the text content from.
ns (str): The namespace of the XML element.
- name_spaces (Dict[str, str]):
- A dictionary mapping namespace prefixes to their URIs.
+ name_spaces (Dict[str, str]): A dictionary mapping namespace prefixes to their
+ URIs.
node_name (str): The name of the XML node.
kwarg (str): The name of the keyword argument to store the text content in.
- classes (Tuple[known_types, ...]): A tuple of known types.
+ classes (Tuple[Type[object], ...]): A tuple of known types.
strict (bool): A flag indicating whether to enforce strict parsing rules.
Returns:
@@ -453,7 +622,7 @@ def subelement_text_kwarg(
name_spaces: Dict[str, str],
node_name: str,
kwarg: str,
- classes: Tuple[known_types, ...],
+ classes: Tuple[Type[object], ...],
strict: bool,
) -> Dict[str, str]:
"""
@@ -466,7 +635,7 @@ def subelement_text_kwarg(
name_spaces (Dict[str, str]): A dictionary of namespace prefixes and URIs.
node_name (str): The name of the subelement.
kwarg (str): The key to use in the returned dictionary.
- classes (Tuple[known_types, ...]): A tuple of known types.
+ classes (Tuple[Type[object], ...]): A tuple of known types.
strict (bool): A flag indicating whether to enforce strict parsing.
Returns:
@@ -488,7 +657,7 @@ def attribute_text_kwarg(
name_spaces: Dict[str, str],
node_name: str,
kwarg: str,
- classes: Tuple[known_types, ...],
+ classes: Tuple[Type[object], ...],
strict: bool,
) -> Dict[str, str]:
"""
@@ -501,7 +670,7 @@ def attribute_text_kwarg(
name_spaces (Dict[str, str]): A dictionary mapping namespace prefixes to URIs.
node_name (str): The name of the XML node.
kwarg (str): The name of the keyword argument.
- classes (Tuple[known_types, ...]): A tuple of known types.
+ classes (Tuple[Type[object], ...]): A tuple of known types.
strict (bool): A flag indicating whether to enforce strict parsing.
Returns:
@@ -533,7 +702,7 @@ def subelement_bool_kwarg(
name_spaces: Dict[str, str],
node_name: str,
kwarg: str,
- classes: Tuple[known_types, ...],
+ classes: Tuple[Type[object], ...],
strict: bool,
) -> Dict[str, bool]:
"""
@@ -546,7 +715,7 @@ def subelement_bool_kwarg(
name_spaces (Dict[str, str]): A dictionary mapping namespace prefixes to URIs.
node_name (str): The name of the subelement.
kwarg (str): The name of the keyword argument to store the boolean value.
- classes (Tuple[known_types, ...]): A tuple of known types.
+ classes (Tuple[Type[object], ...]): A tuple of known types.
strict (bool): A flag indicating whether to enforce strict parsing.
Returns:
@@ -584,7 +753,7 @@ def subelement_int_kwarg(
name_spaces: Dict[str, str],
node_name: str,
kwarg: str,
- classes: Tuple[known_types, ...],
+ classes: Tuple[Type[object], ...],
strict: bool,
) -> Dict[str, int]:
"""
@@ -597,7 +766,7 @@ def subelement_int_kwarg(
name_spaces (Dict[str, str]): A dictionary mapping namespace prefixes to URIs.
node_name (str): The name of the subelement.
kwarg (str): The key to use in the returned dictionary.
- classes (Tuple[known_types, ...]): A tuple of known types for error handling.
+ classes (Tuple[Type[object], ...]): A tuple of known types for error handling.
strict (bool): A flag indicating whether to enforce strict parsing.
Returns:
@@ -633,7 +802,7 @@ def attribute_int_kwarg(
name_spaces: Dict[str, str],
node_name: str,
kwarg: str,
- classes: Tuple[known_types, ...],
+ classes: Tuple[Type[object], ...],
strict: bool,
) -> Dict[str, int]:
"""
@@ -646,7 +815,7 @@ def attribute_int_kwarg(
name_spaces (Dict[str, str]): A dictionary mapping namespace prefixes to URIs.
node_name (str): The name of the XML node containing the attribute.
kwarg (str): The name of the keyword argument to store the extracted attribute.
- classes (Tuple[known_types, ...]): A tuple of known types (unused).
+ classes (Tuple[Type[object], ...]): A tuple of known types (unused).
strict (bool): A flag indicating whether to raise an exception (unused).
Returns:
@@ -665,7 +834,7 @@ def subelement_float_kwarg(
name_spaces: Dict[str, str],
node_name: str,
kwarg: str,
- classes: Tuple[known_types, ...],
+ classes: Tuple[Type[object], ...],
strict: bool,
) -> Dict[str, float]:
"""
@@ -678,7 +847,7 @@ def subelement_float_kwarg(
name_spaces (Dict[str, str]): A dictionary of namespace prefixes and URIs.
node_name (str): The name of the subelement.
kwarg (str): The name of the keyword argument to store the float value.
- classes (Tuple[known_types, ...]): A tuple of known types for error handling.
+ classes (Tuple[Type[object], ...]): A tuple of known types for error handling.
strict (bool): A flag indicating whether to raise an error.
Returns:
@@ -714,7 +883,7 @@ def attribute_float_kwarg(
name_spaces: Dict[str, str],
node_name: str,
kwarg: str,
- classes: Tuple[known_types, ...],
+ classes: Tuple[Type[object], ...],
strict: bool,
) -> Dict[str, float]:
"""
@@ -727,7 +896,7 @@ def attribute_float_kwarg(
name_spaces (Dict[str, str]): A dictionary of namespace prefixes and URIs.
node_name (str): The name of the attribute.
kwarg (str): The name of the keyword argument to store the converted float.
- classes (Tuple[known_types, ...]): A tuple of known types for error handling.
+ classes (Tuple[Type[object], ...]): A tuple of known types for error handling.
strict (bool): A flag indicating whether to raise an error for invalid values.
Returns:
@@ -768,7 +937,7 @@ def subelement_enum_kwarg(
name_spaces: Dict[str, str],
node_name: str,
kwarg: str,
- classes: Tuple[known_types, ...],
+ classes: Tuple[Type[object], ...],
strict: bool,
) -> Dict[str, Enum]:
"""
@@ -781,7 +950,7 @@ def subelement_enum_kwarg(
name_spaces (Dict[str, str]): A dictionary of namespace prefixes and URIs.
node_name (str): The name of the subelement.
kwarg (str): The name of the keyword argument to store the extracted value.
- classes (Tuple[known_types, ...]): A tuple of enumerated value classes.
+ classes (Tuple[Type[object], ...]): A tuple of enumerated value classes.
strict (bool): A flag indicating whether to raise an exception.
Returns:
@@ -826,7 +995,7 @@ def attribute_enum_kwarg(
name_spaces: Dict[str, str],
node_name: str,
kwarg: str,
- classes: Tuple[known_types, ...],
+ classes: Tuple[Type[object], ...],
strict: bool,
) -> Dict[str, Enum]:
"""
@@ -839,7 +1008,7 @@ def attribute_enum_kwarg(
name_spaces (Dict[str, str]): A dictionary of namespace prefixes and their URIs.
node_name (str): The name of the XML node.
kwarg (str): The name of the keyword argument.
- classes (Tuple[known_types, ...]): A tuple of enum classes.
+ classes (Tuple[Type[object], ...]): A tuple of enum classes.
strict (bool): A flag indicating whether to raise an error for invalid values.
Returns:
@@ -869,6 +1038,98 @@ def attribute_enum_kwarg(
return {}
+def datetime_subelement_kwarg(
+ *,
+ element: Element,
+ ns: str,
+ name_spaces: Dict[str, str],
+ node_name: str,
+ kwarg: str,
+ classes: Tuple[Type[object], ...],
+ strict: bool,
+) -> Dict[str, List["KmlDateTime"]]:
+ """Extract a KML datetime from a subelement of an XML element."""
+ cls = classes[0]
+ node = element.find(f"{ns}{node_name}")
+ if node is None:
+ return {}
+ node_text = node.text.strip() if node.text else ""
+ if node_text:
+ try:
+ return {kwarg: cls.parse(node_text)} # type: ignore[attr-defined]
+ except ValueError as exc:
+ handle_error(
+ error=exc,
+ strict=strict,
+ element=element,
+ node=node,
+ expected="DateTime",
+ )
+ return {}
+
+
+def datetime_subelement_list_kwarg(
+ *,
+ element: Element,
+ ns: str,
+ name_spaces: Dict[str, str],
+ node_name: str,
+ kwarg: str,
+ classes: Tuple[Type[object], ...],
+ strict: bool,
+) -> Dict[str, List["KmlDateTime"]]:
+ """Extract a list of KML datetime values from subelements of an XML element."""
+ args_list: List[KmlDateTime] = []
+ cls = classes[0]
+ if subelements := element.findall(f"{ns}{node_name}"):
+ for subelement in subelements:
+ try:
+ args_list.append(
+ cls.parse(subelement.text), # type: ignore[attr-defined]
+ )
+ except ValueError as exc: # noqa: PERF203
+ handle_error(
+ error=exc,
+ strict=strict,
+ element=element,
+ node=subelement,
+ expected="DateTime",
+ )
+ return {kwarg: args_list} if args_list else {}
+
+
+def coords_subelement_list_kwarg(
+ *,
+ element: Element,
+ ns: str,
+ name_spaces: Dict[str, str],
+ node_name: str,
+ kwarg: str,
+ classes: Tuple[Type[object], ...],
+ strict: bool,
+) -> Dict[str, List[PointType]]:
+ """Extract a list of KML coordinate values from subelements of an XML element."""
+ args_list: List[PointType] = []
+ if subelements := element.findall(f"{ns}{node_name}"):
+ for subelement in subelements:
+ if subelement.text:
+ try:
+ coords = cast(
+ PointType,
+ tuple(float(coord) for coord in subelement.text.split()),
+ )
+ args_list.append(coords)
+ except ValueError as exc:
+ handle_error(
+ error=exc,
+ strict=strict,
+ element=element,
+ node=subelement,
+ expected="Coordinates",
+ )
+ return {kwarg: args_list} if args_list else {}
+
+
def xml_subelement_kwarg(
*,
element: Element,
@@ -876,9 +1137,9 @@ def xml_subelement_kwarg(
name_spaces: Dict[str, str],
node_name: str,
kwarg: str,
- classes: Tuple[known_types, ...],
+ classes: Tuple[Type[object], ...],
strict: bool,
-) -> Dict[str, _XMLObject]:
+) -> Dict[str, "_XMLObject"]:
"""
Return the subelement of the given XML element based on the provided parameters.
@@ -889,21 +1150,22 @@ def xml_subelement_kwarg(
name_spaces (Dict[str, str]): A dictionary mapping namespace prefixes to their URIs.
node_name (str): The name of the XML node to search for.
kwarg (str): The name of the keyword argument to store the found subelement.
- classes (Tuple[known_types, ...]): A tuple of classes that represent the types.
+ classes (Tuple[Type[object], ...]): A tuple of classes that represent the types.
strict (bool): A flag indicating whether to enforce strict parsing rules.
Returns:
-------
- Dict[str, _XMLObject]: A dictionary containing the found subelement as the value
+ Dict[str, "_XMLObject"]: A dictionary containing the found subelement as the value
of the specified keyword argument.
"""
for cls in classes:
- assert issubclass(cls, _XMLObject) # noqa: S101
- subelement = element.find(f"{ns}{cls.get_tag_name()}")
+ subelement = element.find(
+ f"{ns}{cls.get_tag_name()}", # type: ignore[attr-defined]
+ )
if subelement is not None:
return {
- kwarg: cls.class_from_element(
+ kwarg: cls.class_from_element( # type: ignore[attr-defined]
ns=ns,
name_spaces=name_spaces,
element=subelement,
@@ -920,9 +1182,9 @@ def xml_subelement_list_kwarg(
name_spaces: Dict[str, str],
node_name: str,
kwarg: str,
- classes: Tuple[known_types, ...],
+ classes: Tuple[Type[object], ...],
strict: bool,
-) -> Dict[str, List[_XMLObject]]:
+) -> Dict[str, List["_XMLObject"]]:
"""
Return a dictionary with the specified keyword argument and its list of subelements.
@@ -933,12 +1195,12 @@ def xml_subelement_list_kwarg(
name_spaces (Dict[str, str]): A dictionary mapping namespace prefixes to URIs.
node_name (str): The name of the XML node to search for.
kwarg (str): The name of the keyword argument to store the found subelements.
- classes (Tuple[known_types, ...]): A tuple of classes that represent the types.
+ classes (Tuple[Type[object], ...]): A tuple of classes that represent the types.
strict (bool): A flag indicating whether to enforce strict parsing rules.
Returns:
-------
- Dict[str, List[_XMLObject]]: A dictionary containing the specified keyword
+ Dict[str, List["_XMLObject"]]: A dictionary containing the specified keyword
argument and its list of subelements.
"""
@@ -946,11 +1208,12 @@ def xml_subelement_list_kwarg(
assert node_name is not None # noqa: S101
assert name_spaces is not None # noqa: S101
for obj_class in classes:
- assert issubclass(obj_class, _XMLObject) # noqa: S101
- if subelements := element.findall(f"{ns}{obj_class.get_tag_name()}"):
+ if subelements := element.findall(
+ f"{ns}{obj_class.get_tag_name()}", # type: ignore[attr-defined]
+ ):
args_list.extend(
[
- obj_class.class_from_element(
+ obj_class.class_from_element( # type: ignore[attr-defined]
ns=ns,
name_spaces=name_spaces,
element=subelement,
diff --git a/fastkml/kml.py b/fastkml/kml.py
index 08687679..7c5dab89 100644
--- a/fastkml/kml.py
+++ b/fastkml/kml.py
@@ -144,6 +144,7 @@ def etree_element(
node_name="",
precision=precision,
verbosity=verbosity,
+ default=None,
)
return cast(Element, root)
@@ -152,9 +153,6 @@ def append(
kmlobj: kml_children,
) -> None:
"""Append a feature."""
- if kmlobj is self:
- msg = "Cannot append self"
- raise ValueError(msg)
self.features.append(kmlobj)
@classmethod
diff --git a/fastkml/links.py b/fastkml/links.py
index 53486e10..62bd4bc8 100644
--- a/fastkml/links.py
+++ b/fastkml/links.py
@@ -44,14 +44,14 @@ class Link(_BaseObject):
https://developers.google.com/kml/documentation/kmlreference#link
"""
- href: Optional[str]
+ href: str
refresh_mode: Optional[RefreshMode]
refresh_interval: Optional[float]
view_refresh_mode: Optional[ViewRefreshMode]
view_refresh_time: Optional[float]
view_bound_scale: Optional[float]
- view_format: Optional[str]
- http_query: Optional[str]
+ view_format: str
+ http_query: str
def __init__(
self,
@@ -77,14 +77,14 @@ def __init__(
target_id=target_id,
**kwargs,
)
- self.href = href
+ self.href = href or ""
self.refresh_mode = refresh_mode
self.refresh_interval = refresh_interval
self.view_refresh_mode = view_refresh_mode
self.view_refresh_time = view_refresh_time
self.view_bound_scale = view_bound_scale
- self.view_format = view_format
- self.http_query = http_query
+ self.view_format = view_format or ""
+ self.http_query = http_query or ""
def __repr__(self) -> str:
"""Create a string (c)representation for Link."""
@@ -134,33 +134,24 @@ def __bool__(self) -> bool:
Link,
RegistryItem(
ns_ids=("kml",),
- attr_name="view_format",
- node_name="viewFormat",
- classes=(str,),
- get_kwarg=subelement_text_kwarg,
- set_element=text_subelement,
- ),
-)
-registry.register(
- Link,
- RegistryItem(
- ns_ids=("kml",),
- attr_name="http_query",
- node_name="httpQuery",
- classes=(str,),
- get_kwarg=subelement_text_kwarg,
- set_element=text_subelement,
+ attr_name="refresh_mode",
+ node_name="refreshMode",
+ classes=(RefreshMode,),
+ get_kwarg=subelement_enum_kwarg,
+ set_element=enum_subelement,
+ default=RefreshMode.on_change,
),
)
registry.register(
Link,
RegistryItem(
ns_ids=("kml",),
- attr_name="refresh_mode",
- node_name="refreshMode",
- classes=(RefreshMode,),
- get_kwarg=subelement_enum_kwarg,
- set_element=enum_subelement,
+ attr_name="refresh_interval",
+ node_name="refreshInterval",
+ classes=(float,),
+ get_kwarg=subelement_float_kwarg,
+ set_element=float_subelement,
+ default=4.0,
),
)
registry.register(
@@ -172,39 +163,54 @@ def __bool__(self) -> bool:
classes=(ViewRefreshMode,),
get_kwarg=subelement_enum_kwarg,
set_element=enum_subelement,
+ default=ViewRefreshMode.never,
),
)
registry.register(
Link,
RegistryItem(
ns_ids=("kml",),
- attr_name="refresh_interval",
- node_name="refreshInterval",
+ attr_name="view_refresh_time",
+ node_name="viewRefreshTime",
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
+ default=4.0,
),
)
registry.register(
Link,
RegistryItem(
ns_ids=("kml",),
- attr_name="view_refresh_time",
- node_name="viewRefreshTime",
+ attr_name="view_bound_scale",
+ node_name="viewBoundScale",
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
+ default=1.0,
),
)
registry.register(
Link,
RegistryItem(
ns_ids=("kml",),
- attr_name="view_bound_scale",
- node_name="viewBoundScale",
- classes=(float,),
- get_kwarg=subelement_float_kwarg,
- set_element=float_subelement,
+ attr_name="view_format",
+ node_name="viewFormat",
+ classes=(str,),
+ get_kwarg=subelement_text_kwarg,
+ set_element=text_subelement,
+ default="BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth]",
+ ),
+)
+registry.register(
+ Link,
+ RegistryItem(
+ ns_ids=("kml",),
+ attr_name="http_query",
+ node_name="httpQuery",
+ classes=(str,),
+ get_kwarg=subelement_text_kwarg,
+ set_element=text_subelement,
),
)
diff --git a/fastkml/overlays.py b/fastkml/overlays.py
index b450c1e7..d5ea5ed3 100644
--- a/fastkml/overlays.py
+++ b/fastkml/overlays.py
@@ -245,6 +245,7 @@ def __repr__(self) -> str:
classes=(str,),
get_kwarg=subelement_text_kwarg,
set_element=text_subelement,
+ default="ffffffff",
),
)
registry.register(
@@ -256,6 +257,7 @@ def __repr__(self) -> str:
classes=(int,),
get_kwarg=subelement_int_kwarg,
set_element=int_subelement,
+ default=0,
),
)
registry.register(
@@ -394,6 +396,7 @@ def __bool__(self) -> bool:
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
+ default=0.0,
),
)
registry.register(
@@ -405,6 +408,7 @@ def __bool__(self) -> bool:
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
+ default=0.0,
),
)
registry.register(
@@ -416,6 +420,7 @@ def __bool__(self) -> bool:
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
+ default=0.0,
),
)
registry.register(
@@ -427,6 +432,7 @@ def __bool__(self) -> bool:
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
+ default=0.0,
),
)
registry.register(
@@ -438,6 +444,7 @@ def __bool__(self) -> bool:
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
+ default=0.0,
),
)
@@ -562,6 +569,7 @@ def __bool__(self) -> bool:
classes=(int,),
get_kwarg=subelement_int_kwarg,
set_element=int_subelement,
+ default=256,
),
)
registry.register(
@@ -595,6 +603,7 @@ def __bool__(self) -> bool:
classes=(GridOrigin,),
get_kwarg=subelement_enum_kwarg,
set_element=enum_subelement,
+ default=GridOrigin.lower_left,
),
)
@@ -831,6 +840,7 @@ def __repr__(self) -> str:
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
+ default=0.0,
),
)
registry.register(
@@ -875,6 +885,7 @@ def __repr__(self) -> str:
classes=(Shape,),
get_kwarg=subelement_enum_kwarg,
set_element=enum_subelement,
+ default=Shape.rectangle,
),
)
@@ -900,6 +911,8 @@ class LatLonBox(_XMLObject):
Specifies a rotation of the overlay about its center, in degrees.
Values can be ±180. The default is 0 (north).
Rotations are specified in a counterclockwise direction.
+
+ https://developers.google.com/kml/documentation/kmlreference#latlonbox
"""
_default_nsid = config.KML
@@ -1042,6 +1055,7 @@ def __bool__(self) -> bool:
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
+ default=0.0,
),
)
@@ -1246,6 +1260,7 @@ def __repr__(self) -> str:
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
+ default=0.0,
),
)
registry.register(
@@ -1257,6 +1272,7 @@ def __repr__(self) -> str:
classes=(AltitudeMode,),
get_kwarg=subelement_enum_kwarg,
set_element=enum_subelement,
+ default=AltitudeMode.clamp_to_ground,
),
)
registry.register(
diff --git a/fastkml/registry.py b/fastkml/registry.py
index d80aaff0..6dd36bb5 100644
--- a/fastkml/registry.py
+++ b/fastkml/registry.py
@@ -15,7 +15,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""Registry for XML objects."""
from dataclasses import dataclass
-from enum import Enum
from typing import TYPE_CHECKING
from typing import Any
from typing import Dict
@@ -23,7 +22,6 @@
from typing import Optional
from typing import Tuple
from typing import Type
-from typing import Union
from typing_extensions import Protocol
@@ -34,16 +32,6 @@
from fastkml.base import _XMLObject
-known_types = Union[
- Type["_XMLObject"],
- Type[Enum],
- Type[bool],
- Type[int],
- Type[str],
- Type[float],
-]
-
-
class GetKWArgs(Protocol):
def __call__(
self,
@@ -53,7 +41,7 @@ def __call__(
name_spaces: Dict[str, str],
node_name: str,
kwarg: str,
- classes: Tuple[known_types, ...],
+ classes: Tuple[Type[object], ...],
strict: bool,
) -> Dict[str, Any]: ...
@@ -67,7 +55,8 @@ def __call__(
attr_name: str,
node_name: str,
precision: Optional[int],
- verbosity: Optional[Verbosity],
+ verbosity: Verbosity,
+ default: Any,
) -> None: ...
@@ -76,11 +65,12 @@ class RegistryItem:
"""A registry item."""
ns_ids: Tuple[str, ...]
- classes: Tuple[known_types, ...]
+ classes: Tuple[Type[object], ...]
attr_name: str
get_kwarg: GetKWArgs
set_element: SetElement
node_name: str
+ default: Any = None
class Registry:
diff --git a/fastkml/schema/atom-author-link.xsd b/fastkml/schema/atom-author-link.xsd
new file mode 100644
index 00000000..b3d77ade
--- /dev/null
+++ b/fastkml/schema/atom-author-link.xsd
@@ -0,0 +1,66 @@
+
+
+
+
+ atom-author-link.xsd 2008-01-23
+ There is no official atom XSD. This XSD is created based on:
+ http://atompub.org/2005/08/17/atom.rnc. A subset of Atom as used in the
+ ogckml22.xsd is defined here.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/fastkml/schema/kml22gx.xsd b/fastkml/schema/kml22gx.xsd
new file mode 100644
index 00000000..6e8e74a2
--- /dev/null
+++ b/fastkml/schema/kml22gx.xsd
@@ -0,0 +1,329 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/schema/ogckml22.xsd b/fastkml/schema/ogckml22.xsd
similarity index 99%
rename from schema/ogckml22.xsd
rename to fastkml/schema/ogckml22.xsd
index 3496aef5..1a8086d4 100644
--- a/schema/ogckml22.xsd
+++ b/fastkml/schema/ogckml22.xsd
@@ -1,6 +1,7 @@
+ schemaLocation="xAL.xsd"/>
+
+
+
diff --git a/fastkml/schema/xAL.xsd b/fastkml/schema/xAL.xsd
new file mode 100644
index 00000000..53eb7428
--- /dev/null
+++ b/fastkml/schema/xAL.xsd
@@ -0,0 +1,1680 @@
+
+
+
+
+ xAL: eXtensible Address Language
+This is an XML document type definition (DTD) for
+defining addresses.
+Original Date of Creation: 1 March 2001
+Copyright(c) 2000, OASIS. All Rights Reserved [http://www.oasis-open.org]
+Contact: Customer Information Quality Technical Committee, OASIS
+http://www.oasis-open.org/committees/ciq
+VERSION: 2.0 [MAJOR RELEASE] Date of Creation: 01 May 2002
+Last Update: 24 July 2002
+Previous Version: 1.3
+
+
+ Common Attributes:Type - If not documented then it means, possible values of Type not limited to: Official, Unique, Abbreviation, OldName, Synonym
+Code:Address element codes are used by groups like postal groups like ECCMA, ADIS, UN/PROLIST for postal services
+
+
+
+
+ Used by postal services to encode the name of the element.
+
+
+
+
+
+ Root element for a list of addresses
+
+
+
+
+
+
+
+
+ Specific to DTD to specify the version number of DTD
+
+
+
+
+
+
+
+ This container defines the details of the address. Can define multiple addresses including tracking address history
+
+
+
+
+
+
+ Postal authorities use specific postal service data to expedient delivery of mail
+
+
+
+
+
+ A unique identifier of an address assigned by postal authorities. Example: DPID in Australia
+
+
+
+
+ Type of identifier. eg. DPID as in Australia
+
+
+
+
+
+
+
+
+
+ Directly affects postal service distribution
+
+
+
+
+ Specific to postal service
+
+
+
+
+
+
+
+
+ Required for some postal services
+
+
+
+
+ Specific to postal service
+
+
+
+
+
+
+
+
+ Required for some postal services
+
+
+
+
+ Specific to postal service
+
+
+
+
+
+
+
+
+ Used for sorting addresses. Values may for example be CEDEX 16 (France)
+
+
+
+
+ Specific to postal service
+
+
+
+
+
+
+
+ Latitude of delivery address
+
+
+
+
+ Specific to postal service
+
+
+
+
+
+
+
+
+ Latitude direction of delivery address;N = North and S = South
+
+
+
+ Specific to postal service
+
+
+
+
+
+
+
+
+ Longtitude of delivery address
+
+
+
+
+ Specific to postal service
+
+
+
+
+
+
+
+
+ Longtitude direction of delivery address;N=North and S=South
+
+
+
+
+ Specific to postal service
+
+
+
+
+
+
+
+
+ any postal service elements not covered by the container can be represented using this element
+
+
+
+
+ Specific to postal service
+
+
+
+
+
+
+
+
+
+
+ USPS, ECMA, UN/PROLIST, etc
+
+
+
+
+
+
+
+ Use the most suitable option. Country contains the most detailed information while Locality is missing Country and AdminArea
+
+
+
+ Address as one line of free text
+
+
+
+
+ Postal, residential, corporate, etc
+
+
+
+
+
+
+
+
+ Container for Address lines
+
+
+
+
+ Specification of a country
+
+
+
+
+
+
+ A country code according to the specified scheme
+
+
+
+
+ Country code scheme possible values, but not limited to: iso.3166-2, iso.3166-3 for two and three character country codes.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Type of address. Example: Postal, residential,business, primary, secondary, etc
+
+
+
+
+ Moved, Living, Investment, Deceased, etc..
+
+
+
+
+ Start Date of the validity of address
+
+
+
+
+ End date of the validity of address
+
+
+
+
+ Communication, Contact, etc.
+
+
+
+
+
+ Key identifier for the element for not reinforced references from other elements. Not required to be unique for the document to be valid, but application may get confused if not unique. Extend this schema adding unique constraint if needed.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Occurrence of the building name before/after the type. eg. EGIS BUILDING where name appears before type
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the dependent locality
+
+
+
+
+
+
+
+
+
+ Number of the dependent locality. Some areas are numbered. Eg. SECTOR 5 in a Suburb as in India or SOI SUKUMVIT 10 as in Thailand
+
+
+
+
+ Eg. SECTOR occurs before 5 in SECTOR 5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Specification of a large mail user address. Examples of large mail users are postal companies, companies in France with a cedex number, hospitals and airports with their own post code. Large mail user addresses do not have a street name with premise name or premise number in countries like Netherlands. But they have a POBox and street also in countries like France
+
+
+
+
+
+ A Postal van is specific for a route as in Is`rael, Rural route
+
+
+
+
+
+
+
+ Dependent localities are Districts within cities/towns, locality divisions, postal
+divisions of cities, suburbs, etc. DependentLocality is a recursive element, but no nesting deeper than two exists (Locality-DependentLocality-DependentLocality).
+
+
+
+
+
+
+
+ City or IndustrialEstate, etc
+
+
+
+
+ Postal or Political - Sometimes locations must be distinguished between postal system, and physical locations as defined by a political system
+
+
+
+
+ "VIA" as in Hill Top VIA Parish where Parish is a locality and Hill Top is a dependent locality
+
+
+
+
+ Eg. Erode (Dist) where (Dist) is the Indicator
+
+
+
+
+
+
+
+
+
+ Name of the firm
+
+
+
+
+
+
+
+
+
+
+ A MailStop is where the the mail is delivered to within a premise/subpremise/firm or a facility.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the large mail user. eg. Smith Ford International airport
+
+
+
+
+ Airport, Hospital, etc
+
+
+
+
+
+
+
+
+ Specification of the identification number of a large mail user. An example are the Cedex codes in France.
+
+
+
+
+ CEDEX Code
+
+
+
+
+ eg. Building 429 in which Building is the Indicator
+
+
+
+
+
+
+
+
+ Name of the building
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the the Mail Stop. eg. MSP, MS, etc
+
+
+
+
+
+
+
+
+
+ Number of the Mail stop. eg. 123 in MS 123
+
+
+
+
+ "-" in MS-123
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the Postal Route
+
+
+
+
+
+
+
+
+
+ Number of the Postal Route
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the SubPremise
+
+
+
+
+
+ EGIS Building where EGIS occurs before Building
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name of the SubPremise Location. eg. LOBBY, BASEMENT, GROUND FLOOR, etc...
+
+
+
+
+
+
+
+ Specification of the identifier of a sub-premise. Examples of sub-premises are apartments and suites. sub-premises in a building are often uniquely identified by means of consecutive
+identifiers. The identifier can be a number, a letter or any combination of the two. In the latter case, the identifier includes exactly one variable (range) part, which is either a
+number or a single letter that is surrounded by fixed parts at the left (prefix) or the right (postfix).
+
+
+
+
+ "TH" in 12TH which is a floor number, "NO." in NO.1, "#" in APT #12, etc.
+
+
+
+
+ "No." occurs before 1 in No.1, or TH occurs after 12 in 12TH
+
+
+
+
+
+
+
+
+
+
+ 12TH occurs "before" FLOOR (a type of subpremise) in 12TH FLOOR
+
+
+
+
+
+
+
+
+
+
+ "/" in 12/14 Archer Street where 12 is sub-premise number and 14 is premise number
+
+
+
+
+
+
+
+
+
+
+ Prefix of the sub premise number. eg. A in A-12
+
+
+
+
+ A-12 where 12 is number and A is prefix and "-" is the separator
+
+
+
+
+
+
+
+
+
+ Suffix of the sub premise number. eg. A in 12A
+
+
+
+
+ 12-A where 12 is number and A is suffix and "-" is the separator
+
+
+
+
+
+
+
+
+
+ Name of the building
+
+
+
+
+ Specification of a firm, company, organization, etc. It can be specified as part of an address that contains a street or a postbox. It is therefore different from a large mail user address, which contains no street.
+
+
+
+
+ A MailStop is where the the mail is delivered to within a premise/subpremise/firm or a facility.
+
+
+
+
+
+ Specification of a single sub-premise. Examples of sub-premises are apartments and suites.
+Each sub-premise should be uniquely identifiable. SubPremiseType: Specification of the name of a sub-premise type. Possible values not limited to: Suite, Apartment, Floor, Unknown
+Multiple levels within a premise by recursively calling SubPremise Eg. Level 4, Suite 2, Block C
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Free format address representation. An address can have more than one line. The order of the AddressLine elements must be preserved.
+
+
+
+
+ Defines the type of address line. eg. Street, Address Line 1, etc.
+
+
+
+
+
+
+
+
+ Locality is one level lower than adminisstrative area. Eg.: cities, reservations and any other built-up areas.
+
+
+
+
+
+
+ Name of the locality
+
+
+
+
+
+
+
+
+
+
+
+ Specification of a large mail user address. Examples of large mail users are postal companies, companies in France with a cedex number, hospitals and airports with their own post code. Large mail user addresses do not have a street name with premise name or premise number in countries like Netherlands. But they have a POBox and street also in countries like France
+
+
+
+
+
+ A Postal van is specific for a route as in Is`rael, Rural route
+
+
+
+
+
+
+
+ Dependent localities are Districts within cities/towns, locality divisions, postal
+divisions of cities, suburbs, etc. DependentLocality is a recursive element, but no nesting deeper than two exists (Locality-DependentLocality-DependentLocality).
+
+
+
+
+
+
+
+ Possible values not limited to: City, IndustrialEstate, etc
+
+
+
+
+ Postal or Political - Sometimes locations must be distinguished between postal system, and physical locations as defined by a political system
+
+
+
+
+ Erode (Dist) where (Dist) is the Indicator
+
+
+
+
+
+
+
+ Specification of a thoroughfare. A thoroughfare could be a rd, street, canal, river, etc. Note dependentlocality in a street. For example, in some countries, a large street will
+have many subdivisions with numbers. Normally the subdivision name is the same as the road name, but with a number to identify it. Eg. SOI SUKUMVIT 3, SUKUMVIT RD, BANGKOK
+
+
+
+
+
+
+
+
+ A container to represent a range of numbers (from x thru y)for a thoroughfare. eg. 1-2 Albert Av
+
+
+
+
+
+
+ Starting number in the range
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Ending number in the range
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Thoroughfare number ranges are odd or even
+
+
+
+
+
+
+
+
+
+
+ "No." No.12-13
+
+
+
+
+ "-" in 12-14 or "Thru" in 12 Thru 14 etc.
+
+
+
+
+ No.12-14 where "No." is before actual street number
+
+
+
+
+
+
+
+
+
+
+ 23-25 Archer St, where number appears before name
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ North Baker Street, where North is the pre-direction. The direction appears before the name.
+
+
+
+
+ Appears before the thoroughfare name. Ed. Spanish: Avenida Aurora, where Avenida is the leading type / French: Rue Moliere, where Rue is the leading type.
+
+
+
+
+ Specification of the name of a Thoroughfare (also dependant street name): street name, canal name, etc.
+
+
+
+
+ Appears after the thoroughfare name. Ed. British: Baker Lane, where Lane is the trailing type.
+
+
+
+
+ 221-bis Baker Street North, where North is the post-direction. The post-direction appears after the name.
+
+
+
+
+ DependentThroughfare is related to a street; occurs in GB, IE, ES, PT
+
+
+
+
+
+
+ North Baker Street, where North is the pre-direction. The direction appears before the name.
+
+
+
+
+ Appears before the thoroughfare name. Ed. Spanish: Avenida Aurora, where Avenida is the leading type / French: Rue Moliere, where Rue is the leading type.
+
+
+
+
+ Specification of the name of a Thoroughfare (also dependant street name): street name, canal name, etc.
+
+
+
+
+ Appears after the thoroughfare name. Ed. British: Baker Lane, where Lane is the trailing type.
+
+
+
+
+ 221-bis Baker Street North, where North is the post-direction. The post-direction appears after the name.
+
+
+
+
+
+
+
+
+
+
+
+ Dependent localities are Districts within cities/towns, locality divisions, postal
+divisions of cities, suburbs, etc. DependentLocality is a recursive element, but no nesting deeper than two exists (Locality-DependentLocality-DependentLocality).
+
+
+
+
+
+ Specification of a firm, company, organization, etc. It can be specified as part of an address that contains a street or a postbox. It is therefore different from
+a large mail user address, which contains no street.
+
+
+
+
+
+
+
+
+
+ Does this thoroughfare have a a dependent thoroughfare? Corner of street X, etc
+
+
+
+
+
+
+
+
+
+
+ Corner of, Intersection of
+
+
+
+
+ Corner of Street1 AND Street 2 where AND is the Connector
+
+
+
+
+ STS in GEORGE and ADELAIDE STS, RDS IN A and B RDS, etc. Use only when both the street types are the same
+
+
+
+
+
+
+
+ Examples of administrative areas are provinces counties, special regions (such as "Rijnmond"), etc.
+
+
+
+
+
+
+ Name of the administrative area. eg. MI in USA, NSW in Australia
+
+
+
+
+
+
+
+
+
+ Specification of a sub-administrative area. An example of a sub-administrative areas is a county. There are two places where the name of an administrative
+area can be specified and in this case, one becomes sub-administrative area.
+
+
+
+
+
+
+ Name of the sub-administrative area
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Province or State or County or Kanton, etc
+
+
+
+
+ Postal or Political - Sometimes locations must be distinguished between postal system, and physical locations as defined by a political system
+
+
+
+
+ Erode (Dist) where (Dist) is the Indicator
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Province or State or County or Kanton, etc
+
+
+
+
+ Postal or Political - Sometimes locations must be distinguished between postal system, and physical locations as defined by a political system
+
+
+
+
+ Erode (Dist) where (Dist) is the Indicator
+
+
+
+
+
+
+
+ Specification of a post office. Examples are a rural post office where post is delivered and a post office containing post office boxes.
+
+
+
+
+
+
+
+ Specification of the name of the post office. This can be a rural postoffice where post is delivered or a post office containing post office boxes.
+
+
+
+
+
+
+
+
+
+ Specification of the number of the postoffice. Common in rural postoffices
+
+
+
+
+ MS in MS 62, # in MS # 12, etc.
+
+
+
+
+ MS occurs before 62 in MS 62
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A Postal van is specific for a route as in Is`rael, Rural route
+
+
+
+
+
+
+
+
+ Could be a Mobile Postoffice Van as in Isreal
+
+
+
+
+ eg. Kottivakkam (P.O) here (P.O) is the Indicator
+
+
+
+
+
+
+
+ PostalCode is the container element for either simple or complex (extended) postal codes. Type: Area Code, Postcode, etc.
+
+
+
+
+
+
+ Specification of a postcode. The postcode is formatted according to country-specific rules. Example: SW3 0A8-1A, 600074, 2067
+
+
+
+
+ Old Postal Code, new code, etc
+
+
+
+
+
+
+
+
+ Examples are: 1234 (USA), 1G (UK), etc.
+
+
+
+
+ Delivery Point Suffix, New Postal Code, etc..
+
+
+
+
+ The separator between postal code number and the extension. Eg. "-"
+
+
+
+
+
+
+
+
+ A post town is not the same as a locality. A post town can encompass a collection of (small) localities. It can also be a subpart of a locality. An actual post town in Norway is "Bergen".
+
+
+
+
+
+
+ Name of the post town
+
+
+
+
+
+
+
+
+
+ GENERAL PO in MIAMI GENERAL PO
+
+
+
+
+
+
+
+
+
+ eg. village, town, suburb, etc
+
+
+
+
+
+
+
+
+
+ Area Code, Postcode, Delivery code as in NZ, etc
+
+
+
+
+
+
+
+ Specification of a postbox like mail delivery point. Only a single postbox number can be specified. Examples of postboxes are POBox, free mail numbers, etc.
+
+
+
+
+
+
+ Specification of the number of a postbox
+
+
+
+
+
+
+
+
+ Specification of the prefix of the post box number. eg. A in POBox:A-123
+
+
+
+
+ A-12 where 12 is number and A is prefix and "-" is the separator
+
+
+
+
+
+
+
+
+ Specification of the suffix of the post box number. eg. A in POBox:123A
+
+
+
+
+ 12-A where 12 is number and A is suffix and "-" is the separator
+
+
+
+
+
+
+
+
+ Some countries like USA have POBox as 12345-123
+
+
+
+
+ "-" is the NumberExtensionSeparator in POBOX:12345-123
+
+
+
+
+
+
+
+ Specification of a firm, company, organization, etc. It can be specified as part of an address that contains a street or a postbox. It is therefore different from
+a large mail user address, which contains no street.
+
+
+
+
+
+
+
+ Possible values are, not limited to: POBox and Freepost.
+
+
+
+
+ LOCKED BAG NO:1234 where the Indicator is NO: and Type is LOCKED BAG
+
+
+
+
+
+
+
+ Subdivision in the firm: School of Physics at Victoria University (School of Physics is the department)
+
+
+
+
+
+
+ Specification of the name of a department.
+
+
+
+
+
+
+
+
+
+ A MailStop is where the the mail is delivered to within a premise/subpremise/firm or a facility.
+
+
+
+
+
+
+
+ School in Physics School, Division in Radiology division of school of physics
+
+
+
+
+
+
+
+ Specification of a single premise, for example a house or a building. The premise as a whole has a unique premise (house) number or a premise name. There could be more than
+one premise in a street referenced in an address. For example a building address near a major shopping centre or raiwlay station
+
+
+
+
+
+
+ Specification of the name of the premise (house, building, park, farm, etc). A premise name is specified when the premise cannot be addressed using a street name plus premise (house) number.
+
+
+
+
+
+ EGIS Building where EGIS occurs before Building, DES JARDINS occurs after COMPLEX DES JARDINS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ LOBBY, BASEMENT, GROUND FLOOR, etc...
+
+
+
+
+
+
+
+
+
+
+ Specification for defining the premise number range. Some premises have number as Building C1-C7
+
+
+
+
+
+ Start number details of the premise number range
+
+
+
+
+
+
+
+
+
+
+
+
+ End number details of the premise number range
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Eg. Odd or even number range
+
+
+
+
+ Eg. No. in Building No:C1-C5
+
+
+
+
+ "-" in 12-14 or "Thru" in 12 Thru 14 etc.
+
+
+
+
+
+ No.12-14 where "No." is before actual street number
+
+
+
+
+
+
+
+
+
+
+ Building 23-25 where the number occurs after building name
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Specification of the name of a building.
+
+
+
+
+
+ Specification of a single sub-premise. Examples of sub-premises are apartments and suites. Each sub-premise should be uniquely identifiable.
+
+
+
+
+ Specification of a firm, company, organization, etc. It can be specified as part of an address that contains a street or a postbox. It is therefore different from a large mail user address, which contains no street.
+
+
+
+
+
+ A MailStop is where the the mail is delivered to within a premise/subpremise/firm or a facility.
+
+
+
+
+
+
+
+
+ COMPLEX in COMPLEX DES JARDINS, A building, station, etc
+
+
+
+
+ STREET, PREMISE, SUBPREMISE, PARK, FARM, etc
+
+
+
+
+ NEAR, ADJACENT TO, etc
+
+
+
+
+ DES, DE, LA, LA, DU in RUE DU BOIS. These terms connect a premise/thoroughfare type and premise/thoroughfare name. Terms may appear with names AVE DU BOIS
+
+
+
+
+
+
+
+ Prefix before the number. A in A12 Archer Street
+
+
+
+ A-12 where 12 is number and A is prefix and "-" is the separator
+
+
+
+
+
+
+
+
+
+ Suffix after the number. A in 12A Archer Street
+
+
+
+
+ NEAR, ADJACENT TO, etc
+ 12-A where 12 is number and A is suffix and "-" is the separator
+
+
+
+
+
+
+
+
+
+ Eg.: 23 Archer street or 25/15 Zero Avenue, etc
+
+
+
+
+ 12 Archer Street is "Single" and 12-14 Archer Street is "Range"
+
+
+
+
+
+
+
+
+
+
+
+ No. in Street No.12 or "#" in Street # 12, etc.
+
+
+
+
+ No.12 where "No." is before actual street number
+
+
+
+
+
+
+
+
+
+
+ 23 Archer St, Archer Street 23, St Archer 23
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Specification of the identifier of the premise (house, building, etc). Premises in a street are often uniquely identified by means of consecutive identifiers. The identifier can be a number, a letter or any combination of the two.
+
+
+
+
+ Building 12-14 is "Range" and Building 12 is "Single"
+
+
+
+
+
+
+
+
+
+
+
+ No. in House No.12, # in #12, etc.
+
+
+
+
+ No. occurs before 12 No.12
+
+
+
+
+
+
+
+
+
+
+ 12 in BUILDING 12 occurs "after" premise type BUILDING
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A in A12
+
+
+
+
+
+
+ A-12 where 12 is number and A is prefix and "-" is the separator
+
+
+
+
+
+
+
+
+
+
+
+ A in 12A
+
+
+
+
+ 12-A where 12 is number and A is suffix and "-" is the separator
+
+
+
+
+
+
+
+
+
+ Specification of the name of a country.
+
+
+
+
+ Old name, new name, etc
+
+
+
+
+
+
+
diff --git a/fastkml/styles.py b/fastkml/styles.py
index d6a4c930..c70de2c7 100644
--- a/fastkml/styles.py
+++ b/fastkml/styles.py
@@ -183,8 +183,7 @@ class _ColorStyle(_BaseObject):
https://developers.google.com/kml/documentation/kmlreference#colorstyle
"""
- id = None
- color = None
+ color: Optional[str] = None
# Color and opacity (alpha) values are expressed in hexadecimal notation.
# The range of values for any one color is 0 to 255 (00 to ff).
# For alpha, 00 is fully transparent and ff is fully opaque.
@@ -257,6 +256,7 @@ def __repr__(self) -> str:
classes=(str,),
get_kwarg=subelement_text_kwarg,
set_element=text_subelement,
+ default="ffffffff",
),
)
registry.register(
@@ -268,6 +268,7 @@ def __repr__(self) -> str:
classes=(ColorMode,),
get_kwarg=subelement_enum_kwarg,
set_element=enum_subelement,
+ default=ColorMode.normal,
),
)
@@ -371,6 +372,7 @@ def get_tag_name(cls) -> str:
classes=(float,),
get_kwarg=attribute_float_kwarg,
set_element=float_attribute,
+ default=0.5,
),
)
registry.register(
@@ -382,6 +384,7 @@ def get_tag_name(cls) -> str:
classes=(float,),
get_kwarg=attribute_float_kwarg,
set_element=float_attribute,
+ default=0.5,
),
)
registry.register(
@@ -393,6 +396,7 @@ def get_tag_name(cls) -> str:
classes=(Units,),
get_kwarg=attribute_enum_kwarg,
set_element=enum_attribute,
+ default=Units.fraction,
),
)
registry.register(
@@ -404,6 +408,7 @@ def get_tag_name(cls) -> str:
classes=(Units,),
get_kwarg=attribute_enum_kwarg,
set_element=enum_attribute,
+ default=Units.fraction,
),
)
@@ -521,6 +526,7 @@ def __bool__(self) -> bool:
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
+ default=1.0,
),
)
registry.register(
@@ -532,6 +538,7 @@ def __bool__(self) -> bool:
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
+ default=0.0,
),
)
registry.register(
@@ -652,6 +659,7 @@ def __bool__(self) -> bool:
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
+ default=1.0,
),
)
@@ -760,6 +768,7 @@ def __bool__(self) -> bool:
classes=(bool,),
get_kwarg=subelement_bool_kwarg,
set_element=bool_subelement,
+ default=True,
),
)
registry.register(
@@ -771,6 +780,7 @@ def __bool__(self) -> bool:
classes=(bool,),
get_kwarg=subelement_bool_kwarg,
set_element=bool_subelement,
+ default=True,
),
)
@@ -872,6 +882,7 @@ def __bool__(self) -> bool:
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
+ default=1.0,
),
)
@@ -1023,6 +1034,7 @@ def __bool__(self) -> bool:
classes=(str,),
get_kwarg=subelement_text_kwarg,
set_element=text_subelement,
+ default="ffffffff",
),
)
registry.register(
@@ -1034,6 +1046,7 @@ def __bool__(self) -> bool:
classes=(str,),
get_kwarg=subelement_text_kwarg,
set_element=text_subelement,
+ default="ff000000",
),
)
registry.register(
@@ -1056,6 +1069,7 @@ def __bool__(self) -> bool:
classes=(DisplayMode,),
get_kwarg=subelement_enum_kwarg,
set_element=enum_subelement,
+ default=DisplayMode.default,
),
)
diff --git a/fastkml/times.py b/fastkml/times.py
index 54c4d0bc..62b06fd9 100644
--- a/fastkml/times.py
+++ b/fastkml/times.py
@@ -13,7 +13,15 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-"""Date and time handling in KML."""
+"""
+Date and time handling in KML.
+
+Any Feature in KML can have time data associated with it.
+This time data has the effect of restricting the visibility of the data set to a given
+time period or point in time.
+
+https://developers.google.com/kml/documentation/time
+"""
import re
from datetime import date
from datetime import datetime
@@ -26,12 +34,14 @@
from fastkml import config
from fastkml.enums import DateTimeResolution
-from fastkml.enums import Verbosity
+from fastkml.helpers import datetime_subelement
+from fastkml.helpers import datetime_subelement_kwarg
from fastkml.kml_base import _BaseObject
-from fastkml.types import Element
+from fastkml.registry import RegistryItem
+from fastkml.registry import registry
# regular expression to parse a gYearMonth string
-# year and month may be separated by a dash or not
+# year and month may be separated by an optional dash
# year is always 4 digits, month is always 2 digits
year_month_day = re.compile(
r"^(?P\d{4})(?:-)?(?P\d{2})?(?:-)?(?P\d{2})?$",
@@ -143,6 +153,11 @@ def parse(cls, datestr: str) -> Optional["KmlDateTime"]:
resolution = DateTimeResolution.datetime
return cls(dt, resolution) if dt else None
+ @classmethod
+ def get_ns_id(cls) -> str:
+ """Return the namespace ID."""
+ return config.KML
+
class _TimePrimitive(_BaseObject):
"""
@@ -154,7 +169,11 @@ class _TimePrimitive(_BaseObject):
class TimeStamp(_TimePrimitive):
- """Represents a single moment in time."""
+ """
+ Represents a single moment in time.
+
+ https://developers.google.com/kml/documentation/kmlreference#timestamp
+ """
def __init__(
self,
@@ -214,55 +233,26 @@ def __bool__(self) -> bool:
"""Return True if the timestamp is valid."""
return bool(self.timestamp)
- def etree_element(
- self,
- precision: Optional[int] = None,
- verbosity: Verbosity = Verbosity.normal,
- ) -> Element:
- """
- Create an ElementTree element representing the TimeStamp object.
-
- Args:
- ----
- precision (Optional[int]): The precision of the timestamp.
- verbosity (Verbosity): The verbosity level of the element.
-
- Returns:
- -------
- Element: The ElementTree element representing the TimeStamp object.
- """
- element = super().etree_element(precision=precision, verbosity=verbosity)
- when = config.etree.SubElement(
- element,
- f"{self.ns}when",
- )
- when.text = str(self.timestamp)
- return element
-
- @classmethod
- def _get_kwargs(
- cls,
- *,
- ns: str,
- name_spaces: Optional[Dict[str, str]] = None,
- element: Element,
- strict: bool,
- ) -> Dict[str, Any]:
- kwargs = super()._get_kwargs(
- ns=ns,
- name_spaces=name_spaces,
- element=element,
- strict=strict,
- )
- when = element.find(f"{ns}when")
- if when is not None:
- kwargs["timestamp"] = KmlDateTime.parse(when.text)
- return kwargs
+registry.register(
+ TimeStamp,
+ item=RegistryItem(
+ ns_ids=("kml", "gx", ""),
+ classes=(KmlDateTime,),
+ attr_name="timestamp",
+ node_name="when",
+ get_kwarg=datetime_subelement_kwarg,
+ set_element=datetime_subelement,
+ ),
+)
class TimeSpan(_TimePrimitive):
- """Represents an extent in time bounded by begin and end dateTimes."""
+ """
+ Represents an extent in time bounded by begin and end dateTimes.
+
+ https://developers.google.com/kml/documentation/kmlreference#timespan
+ """
def __init__(
self,
@@ -321,60 +311,26 @@ def __bool__(self) -> bool:
"""Return True if the begin or end date is valid."""
return bool(self.begin) or bool(self.end)
- def etree_element(
- self,
- precision: Optional[int] = None,
- verbosity: Verbosity = Verbosity.normal,
- ) -> Element:
- """
- Create an Element object representing the time interval.
-
- Args:
- ----
- precision (Optional[int]): The precision of the time values.
- verbosity (Verbosity): The verbosity level for the element.
-
- Returns:
- -------
- Element: The created Element object.
-
- """
- element = super().etree_element(precision=precision, verbosity=verbosity)
- if self.begin is not None: # noqa: SIM102
- if text := str(self.begin):
- begin = config.etree.SubElement(
- element,
- f"{self.ns}begin",
- )
- begin.text = text
- if self.end is not None: # noqa: SIM102
- if text := str(self.end):
- end = config.etree.SubElement(
- element,
- f"{self.ns}end",
- )
- end.text = text
- return element
- @classmethod
- def _get_kwargs(
- cls,
- *,
- ns: str,
- name_spaces: Optional[Dict[str, str]] = None,
- element: Element,
- strict: bool,
- ) -> Dict[str, Any]:
- kwargs = super()._get_kwargs(
- ns=ns,
- name_spaces=name_spaces,
- element=element,
- strict=strict,
- )
- begin = element.find(f"{ns}begin")
- if begin is not None:
- kwargs["begin"] = KmlDateTime.parse(begin.text)
- end = element.find(f"{ns}end")
- if end is not None:
- kwargs["end"] = KmlDateTime.parse(end.text)
- return kwargs
+registry.register(
+ TimeSpan,
+ item=RegistryItem(
+ ns_ids=("kml", "gx", ""),
+ classes=(KmlDateTime,),
+ attr_name="begin",
+ node_name="begin",
+ get_kwarg=datetime_subelement_kwarg,
+ set_element=datetime_subelement,
+ ),
+)
+registry.register(
+ TimeSpan,
+ item=RegistryItem(
+ ns_ids=("kml", "gx", ""),
+ classes=(KmlDateTime,),
+ attr_name="end",
+ node_name="end",
+ get_kwarg=datetime_subelement_kwarg,
+ set_element=datetime_subelement,
+ ),
+)
diff --git a/fastkml/utils.py b/fastkml/utils.py
new file mode 100644
index 00000000..2851cb5c
--- /dev/null
+++ b/fastkml/utils.py
@@ -0,0 +1,91 @@
+"""Fastkml utility functions."""
+
+from typing import Any
+from typing import Generator
+from typing import Optional
+from typing import Tuple
+from typing import Type
+from typing import Union
+
+__all__ = ["find_all", "has_attribute_values"]
+
+
+def has_attribute_values(obj: object, **kwargs: Any) -> bool:
+ """
+ Check if an object has all of the given attribute values.
+
+ Args:
+ ----
+ obj: The object to check.
+ **kwargs: Attributes of the object to match.
+
+ Returns:
+ -------
+ True if the object has the given attribute values, False otherwise.
+
+ """
+ try:
+ return all(getattr(obj, key) == value for key, value in kwargs.items())
+ except AttributeError:
+ return False
+
+
+def find_all(
+ obj: object,
+ *,
+ of_type: Optional[Union[Type[object], Tuple[Type[object], ...]]] = None,
+ **kwargs: Any,
+) -> Generator[object, None, None]:
+ """
+ Find all instances of a given type in a given object.
+
+ Args:
+ ----
+ obj: The object to search.
+ of_type: The type(s) to search for or None for any type.
+ **kwargs: Attributes of the object to match.
+
+ Returns:
+ -------
+ An iterable of all instances of the given type in the given object.
+
+ """
+ if (of_type is None or isinstance(obj, of_type)) and has_attribute_values(
+ obj,
+ **kwargs,
+ ):
+ yield obj
+ try:
+ attrs = (attr for attr in obj.__dict__ if not attr.startswith("_"))
+ except AttributeError:
+ return
+ for attr_name in attrs:
+ attr = getattr(obj, attr_name)
+ try:
+ for item in attr:
+ yield from find_all(item, of_type=of_type, **kwargs)
+ except TypeError:
+ yield from find_all(attr, of_type=of_type, **kwargs)
+
+
+def find(
+ obj: object,
+ *,
+ of_type: Optional[Union[Type[object], Tuple[Type[object], ...]]] = None,
+ **kwargs: Any,
+) -> Optional[object]:
+ """
+ Find the first instance of a given type in a given object.
+
+ Args:
+ ----
+ obj: The object to search.
+ of_type: The type(s) to search for or None for any type.
+ **kwargs: Attributes of the object to match.
+
+ Returns:
+ -------
+ The first instance of the given type in the given object or None if not found.
+
+ """
+ return next(find_all(obj, of_type=of_type, **kwargs), None)
diff --git a/fastkml/validator.py b/fastkml/validator.py
new file mode 100644
index 00000000..c40fdf82
--- /dev/null
+++ b/fastkml/validator.py
@@ -0,0 +1,114 @@
+# Copyright (C) 2024 Christian Ledermann
+#
+# This library is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This library is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+"""Validate KML files against the XML schema."""
+import logging
+import pathlib
+from functools import lru_cache
+from typing import Final
+from typing import Optional
+
+from fastkml import config
+from fastkml.types import Element
+
+logger = logging.getLogger(__name__)
+
+MUTUAL_EXCLUSIVE: Final = "Only one of element and file_to_validate can be provided."
+REQUIRE_ONE_OF: Final = "Either element or file_to_validate must be provided."
+
+
+@lru_cache(maxsize=16)
+def get_schema_parser(
+ schema: Optional[pathlib.Path] = None,
+) -> "config.etree.XMLSchema":
+ """
+ Parse the XML schema.
+
+ Args:
+ ----
+ schema: The path to the XML schema file.
+
+ Returns:
+ -------
+ The parsed XML schema.
+
+ To clear the cache call get_schema_parser.cache_clear().
+
+ """
+ if schema is None:
+ schema = pathlib.Path(__file__).parent / "schema" / "ogckml22.xsd"
+ return config.etree.XMLSchema(config.etree.parse(schema))
+
+
+def validate(
+ *,
+ schema: Optional[pathlib.Path] = None,
+ element: Optional[Element] = None,
+ file_to_validate: Optional[pathlib.Path] = None,
+) -> Optional[bool]:
+ """
+ Validate a KML file against the XML schema.
+
+ Args:
+ ----
+ schema: The path to the XML schema file.
+ element: The element to validate.
+ file_to_validate: The file to validate.
+
+ Returns:
+ -------
+ True if the file or element is valid.
+ Raises an AssertionError if validation fails.
+ Returns None if the schema parser is unavailable.
+
+ """
+ if element is None and file_to_validate is None:
+ raise ValueError(REQUIRE_ONE_OF)
+ if element is not None and file_to_validate is not None:
+ raise ValueError(MUTUAL_EXCLUSIVE)
+
+ try:
+ schema_parser = get_schema_parser(schema)
+ except AttributeError:
+ return None
+
+ if file_to_validate is not None:
+ element = config.etree.parse(file_to_validate)
+
+ try:
+ schema_parser.assert_(element) # noqa: PT009
+ except AssertionError:
+ log = schema_parser.error_log
+ for e in log:
+ try:
+ parent = element.xpath(e.path)[ # type: ignore[union-attr]
+ 0
+ ].getparent()
+ except config.etree.XPathEvalError:
+ parent = element
+ error_in_xml = config.etree.tostring(
+ parent,
+ encoding="UTF-8",
+ pretty_print=True,
+ ).decode(
+ "UTF-8",
+ )
+ logger.error( # noqa: TRY400
+ "Error <%s> in XML:\n %s",
+ e.message,
+ error_in_xml,
+ )
+ raise
+ return True
diff --git a/fastkml/views.py b/fastkml/views.py
index 45ffbd89..5e56d545 100644
--- a/fastkml/views.py
+++ b/fastkml/views.py
@@ -25,8 +25,10 @@
from fastkml.enums import AltitudeMode
from fastkml.helpers import enum_subelement
from fastkml.helpers import float_subelement
+from fastkml.helpers import int_subelement
from fastkml.helpers import subelement_enum_kwarg
from fastkml.helpers import subelement_float_kwarg
+from fastkml.helpers import subelement_int_kwarg
from fastkml.helpers import xml_subelement
from fastkml.helpers import xml_subelement_kwarg
from fastkml.kml_base import _BaseObject
@@ -180,6 +182,7 @@ def __repr__(self) -> str:
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
+ default=0.0,
),
)
registry.register(
@@ -191,6 +194,7 @@ def __repr__(self) -> str:
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
+ default=0.0,
),
)
registry.register(
@@ -202,6 +206,7 @@ def __repr__(self) -> str:
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
+ default=0.0,
),
)
registry.register(
@@ -213,6 +218,7 @@ def __repr__(self) -> str:
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
+ default=0.0,
),
)
registry.register(
@@ -224,6 +230,7 @@ def __repr__(self) -> str:
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
+ default=0.0,
),
)
registry.register(
@@ -235,6 +242,7 @@ def __repr__(self) -> str:
classes=(AltitudeMode,),
get_kwarg=subelement_enum_kwarg,
set_element=enum_subelement,
+ default=AltitudeMode.clamp_to_ground,
),
)
registry.register(
@@ -271,6 +279,8 @@ class Camera(_AbstractView):
Time values in Camera affect historical imagery, sunlight, and the display of
time-stamped features. For more information, read Time with AbstractViews in
the Time and Animation chapter of the Developer's Guide.
+
+ https://developers.google.com/kml/documentation/kmlreference#camera
"""
roll: Optional[float]
@@ -289,7 +299,7 @@ def __init__(
heading: Optional[float] = None,
tilt: Optional[float] = None,
roll: Optional[float] = None,
- altitude_mode: AltitudeMode = AltitudeMode.relative_to_ground,
+ altitude_mode: Optional[AltitudeMode] = None,
time_primitive: Union[TimeSpan, TimeStamp, None] = None,
**kwargs: Any,
) -> None:
@@ -364,6 +374,7 @@ def __repr__(self) -> str:
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
+ default=0.0,
),
)
@@ -395,7 +406,7 @@ def __init__(
heading: Optional[float] = None,
tilt: Optional[float] = None,
range: Optional[float] = None,
- altitude_mode: AltitudeMode = AltitudeMode.relative_to_ground,
+ altitude_mode: Optional[AltitudeMode] = None,
time_primitive: Union[TimeSpan, TimeStamp, None] = None,
**kwargs: Any,
) -> None:
@@ -624,6 +635,7 @@ def __bool__(self) -> bool:
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
+ default=0.0,
),
)
registry.register(
@@ -635,6 +647,7 @@ def __bool__(self) -> bool:
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
+ default=0.0,
),
)
registry.register(
@@ -646,6 +659,7 @@ def __bool__(self) -> bool:
classes=(AltitudeMode,),
get_kwarg=subelement_enum_kwarg,
set_element=enum_subelement,
+ default=AltitudeMode.clamp_to_ground,
),
)
@@ -664,19 +678,19 @@ class Lod(_XMLObject):
_default_nsid = config.KML
- min_lod_pixels: Optional[float]
- max_lod_pixels: Optional[float]
- min_fade_extent: Optional[float]
- max_fade_extent: Optional[float]
+ min_lod_pixels: Optional[int]
+ max_lod_pixels: Optional[int]
+ min_fade_extent: Optional[int]
+ max_fade_extent: Optional[int]
def __init__(
self,
ns: Optional[str] = None,
name_spaces: Optional[Dict[str, str]] = None,
- min_lod_pixels: Optional[float] = None,
- max_lod_pixels: Optional[float] = None,
- min_fade_extent: Optional[float] = None,
- max_fade_extent: Optional[float] = None,
+ min_lod_pixels: Optional[int] = None,
+ max_lod_pixels: Optional[int] = None,
+ min_fade_extent: Optional[int] = None,
+ max_fade_extent: Optional[int] = None,
**kwargs: Any,
) -> None:
"""
@@ -737,8 +751,9 @@ def __bool__(self) -> bool:
attr_name="min_lod_pixels",
node_name="minLodPixels",
classes=(float,),
- get_kwarg=subelement_float_kwarg,
- set_element=float_subelement,
+ get_kwarg=subelement_int_kwarg,
+ set_element=int_subelement,
+ default=256,
),
)
registry.register(
@@ -748,8 +763,9 @@ def __bool__(self) -> bool:
attr_name="max_lod_pixels",
node_name="maxLodPixels",
classes=(float,),
- get_kwarg=subelement_float_kwarg,
- set_element=float_subelement,
+ get_kwarg=subelement_int_kwarg,
+ set_element=int_subelement,
+ default=-1,
),
)
registry.register(
@@ -761,6 +777,7 @@ def __bool__(self) -> bool:
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
+ default=0,
),
)
registry.register(
@@ -772,6 +789,7 @@ def __bool__(self) -> bool:
classes=(float,),
get_kwarg=subelement_float_kwarg,
set_element=float_subelement,
+ default=0,
),
)
diff --git a/pyproject.toml b/pyproject.toml
index d8a2cfbb..dc3c71e3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -9,7 +9,7 @@ authors = [
{ email = "christian.ledermann@gmail.com", name = "Christian Ledermann" },
]
classifiers = [
- "Development Status :: 3 - Alpha",
+ "Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
"Operating System :: OS Independent",
@@ -21,6 +21,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
+ "Programming Language :: Python :: 3.14",
"Topic :: Scientific/Engineering :: GIS",
"Topic :: Text Processing :: Markup :: XML",
"Typing :: Typed",
@@ -46,17 +47,13 @@ complexity = [
"radon",
]
dev = [
- "fastkml[complexity]",
- "fastkml[docs]",
- "fastkml[linting]",
- "fastkml[lxml]",
- "fastkml[tests]",
- "fastkml[typing]",
+ "fastkml[complexity,docs,linting,lxml,tests,typing]",
"pre-commit",
"shapely",
]
docs = [
"Sphinx",
+ "pyshp",
"sphinx-autodoc-typehints",
"sphinx-rtd-theme",
]
@@ -77,8 +74,11 @@ lxml = [
"lxml",
]
tests = [
+ "hypothesis[dateutil]",
"pytest",
"pytest-cov",
+ "pytz",
+ "tzdata",
]
typing = [
"mypy",
@@ -105,7 +105,6 @@ ignore = [
".*",
"examples/*",
"mutmut_config.py",
- "test-requirements.txt",
"tox.ini",
]
@@ -118,6 +117,7 @@ source = [
[tool.coverage.report]
exclude_also = [
"^\\s*\\.\\.\\.$",
+ "class \\w+\\(Protocol\\)\\:",
"except AssertionError:",
"except ImportError:",
"if TYPE_CHECKING:",
@@ -146,10 +146,6 @@ ignore_errors = false
ignore_missing_imports = true
implicit_reexport = false
no_implicit_optional = true
-overrides = [
- { disable_error_code = "attr-defined, union-attr", module = "tests.oldunit_test" },
- { disable_error_code = "union-attr", module = "tests.*" },
-]
show_error_codes = true
strict_equality = true
strict_optional = true
@@ -160,6 +156,18 @@ warn_unreachable = true
warn_unused_configs = true
warn_unused_ignores = true
+[[tool.mypy.overrides]]
+disable_error_code = "attr-defined, union-attr"
+module = "tests.oldunit_test"
+
+[[tool.mypy.overrides]]
+disable_error_code = "attr-defined, union-attr, no-untyped-def, no-untyped-call, arg-type"
+module = "examples.*"
+
+[[tool.mypy.overrides]]
+disable_error_code = "union-attr"
+module = "tests.*"
+
[tool.pyright]
exclude = [
"**/__pycache__",
@@ -187,67 +195,21 @@ ignore = [
"PLR0913",
]
select = [
- "A",
- "AIR",
- "ANN",
- "ARG",
- "ASYNC",
- "B",
- "BLE",
- "C4",
- "C90",
- "COM",
- "CPY",
- "D",
- "DJ",
- "DTZ",
- "E",
- "EM",
- "ERA",
- "EXE",
- "F",
- "FA",
- "FBT",
- "FIX",
- "FLY",
- "FURB",
- "G",
- "I",
- "ICN",
- "INP",
- "INT",
- "ISC",
- "LOG",
- "N",
- "NPY",
- "PD",
- "PERF",
- "PGH",
- "PIE",
- "PL",
- "PT",
- "PTH",
- "PYI",
- "Q",
- "RET",
- "RSE",
- "RUF",
- "S",
- "SIM",
- "SLF",
- "SLOT",
- "T10",
- "T20",
- "TCH",
- "TD",
- "TID",
- "TRY",
- "UP",
- "W",
- "YTT",
+ "ALL",
]
[tool.ruff.lint.extend-per-file-ignores]
+"examples/*.py" = [
+ "ANN001",
+ "ANN201",
+ "D100",
+ "D104",
+ "D401",
+ "ICN001",
+ "INP001",
+ "S311",
+ "T201",
+]
"fastkml/helpers.py" = [
"ARG001",
"PLR0913",
@@ -262,7 +224,13 @@ select = [
"SLF001",
]
"tests/oldunit_test.py" = [
+ "D100",
+ "D200",
+ "D401",
"E501",
+ "FIX",
+ "ICN001",
+ "TD",
]
"tests/repr_eq_test.py" = [
"E501",
diff --git a/tests/atom_test.py b/tests/atom_test.py
index 92c2e968..56635fc5 100644
--- a/tests/atom_test.py
+++ b/tests/atom_test.py
@@ -64,14 +64,14 @@ def test_atom_link_round_trip(self) -> None:
length=3456,
)
- link2 = atom.Link.class_from_string(link.to_string())
+ link2 = atom.Link.from_string(link.to_string())
assert link == link2
assert link.to_string() == link2.to_string()
assert repr(link) == repr(link2)
def test_atom_link_read(self) -> None:
- link = atom.Link.class_from_string(
+ link = atom.Link.from_string(
' ',
@@ -84,12 +84,12 @@ def test_atom_link_read(self) -> None:
assert link.length == 3456
def test_atom_link_read_no_href(self) -> None:
- link = atom.Link.class_from_string(
+ link = atom.Link.from_string(
' ',
)
- assert link.href is None
+ assert link.href == ""
def test_atom_person_ns(self) -> None:
ns = "{http://www.opengis.net/kml/2.2}"
@@ -112,7 +112,7 @@ def test_atom_author(self) -> None:
assert "" in serialized
def test_atom_author_read(self) -> None:
- a = atom.Author.class_from_string(
+ a = atom.Author.from_string(
''
"Nobody http://localhost "
"cl@donotreply.com ",
@@ -131,28 +131,28 @@ def test_atom_author_round_trip(self) -> None:
email="cl@donotreply.com",
)
- a2 = atom.Author.class_from_string(a.to_string())
+ a2 = atom.Author.from_string(a.to_string())
assert a == a2
assert a.to_string() == a2.to_string()
assert repr(a) == repr(a2)
def test_atom_contributor_read_no_name(self) -> None:
- a = atom.Contributor.class_from_string(
+ a = atom.Contributor.from_string(
''
"http://localhost "
"cl@donotreply.com ",
ns="{http://www.w3.org/2005/Atom}",
)
- assert a.name is None
+ assert a.name == ""
assert a.uri == "http://localhost"
assert a.email == "cl@donotreply.com"
def test_atom_contributor_no_name(self) -> None:
a = atom.Contributor(uri="http://localhost", email="cl@donotreply.com")
- assert a.name is None
+ assert a.name == ""
assert "atom:name" not in a.to_string()
def test_atom_contributor_roundtrip(self) -> None:
@@ -163,7 +163,7 @@ def test_atom_contributor_roundtrip(self) -> None:
email="cl@donotreply.com",
)
- a2 = atom.Contributor.class_from_string(a.to_string())
+ a2 = atom.Contributor.from_string(a.to_string())
assert a == a2
assert a.to_string() == a2.to_string()
diff --git a/tests/base.py b/tests/base.py
index 5327191f..250b2480 100644
--- a/tests/base.py
+++ b/tests/base.py
@@ -15,7 +15,7 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""Base classes to run the tests both with the std library and lxml."""
-import xml.etree.ElementTree
+import xml.etree.ElementTree as ET
import pytest
@@ -27,14 +27,15 @@
LXML = False
from fastkml import config
+from fastkml.validator import get_schema_parser
class StdLibrary:
"""Configure test to run with the standard library."""
def setup_method(self) -> None:
- """Always test with the same parser."""
- config.set_etree_implementation(xml.etree.ElementTree)
+ """Ensure to always test with the standard library xml ElementTree parser."""
+ config.set_etree_implementation(ET)
config.set_default_namespaces()
@@ -47,6 +48,7 @@ class Lxml:
"""
def setup_method(self) -> None:
- """Always test with the same parser."""
+ """Ensure to always test with the lxml parse."""
config.set_etree_implementation(lxml.etree)
config.set_default_namespaces()
+ get_schema_parser()
diff --git a/tests/base_test.py b/tests/base_test.py
index 5f1b742f..8388c665 100644
--- a/tests/base_test.py
+++ b/tests/base_test.py
@@ -88,7 +88,7 @@ def test_to_str_empty_ns(self) -> None:
) == '<_BaseObject id="id-0" targetId="target-id-0" />'.replace(" ", "")
def test_from_string(self) -> None:
- be = kml_base._BaseObject.class_from_string(
+ be = kml_base._BaseObject.from_string(
string=(
' '
@@ -98,7 +98,7 @@ def test_from_string(self) -> None:
assert be.target_id == "target-id-0"
def test_from_string_attr_ns_prefix(self) -> None:
- be = kml_base._BaseObject.class_from_string(
+ be = kml_base._BaseObject.from_string(
string=(
' '
@@ -108,7 +108,7 @@ def test_from_string_attr_ns_prefix(self) -> None:
assert be.target_id == "target-id-0"
def test_base_class_from_string(self) -> None:
- be = kml_base._BaseObject.class_from_string(
+ be = kml_base._BaseObject.from_string(
' ',
)
@@ -117,7 +117,7 @@ def test_base_class_from_string(self) -> None:
assert be.ns == "{http://www.opengis.net/kml/2.2}"
def test_base_class_from_empty_string(self) -> None:
- be = kml_base._BaseObject.class_from_string(" ")
+ be = kml_base._BaseObject.from_string(" ")
assert be.id == ""
assert be.target_id == ""
@@ -125,7 +125,7 @@ def test_base_class_from_empty_string(self) -> None:
def test_xml_object_roundtrip(self) -> None:
obj = base._XMLObject()
- obj2 = base._XMLObject.class_from_string(obj.to_string(), ns="")
+ obj2 = base._XMLObject.from_string(obj.to_string(), ns="")
assert obj == obj2
assert str(obj) == obj2.to_string()
@@ -134,7 +134,7 @@ def test_xml_object_roundtrip(self) -> None:
def test_base_object_roundtrip(self) -> None:
obj = kml_base._BaseObject(id="id-0", target_id="target-id-0")
- obj2 = kml_base._BaseObject.class_from_string(obj.to_string())
+ obj2 = kml_base._BaseObject.from_string(obj.to_string())
assert obj == obj2
assert str(obj) == obj2.to_string()
@@ -153,7 +153,7 @@ def test_to_string(self) -> None:
)
def test_from_string(self) -> None:
- be = kml_base._BaseObject.class_from_string(
+ be = kml_base._BaseObject.from_string(
string=(
' \n'
diff --git a/tests/config_test.py b/tests/config_test.py
index a7e722aa..c1ab10ca 100644
--- a/tests/config_test.py
+++ b/tests/config_test.py
@@ -16,7 +16,7 @@
"""Test the configuration options."""
-import xml.etree.ElementTree as ET
+from xml.etree import ElementTree as ET
import pytest
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 00000000..b54dbc5a
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,21 @@
+"""
+Configure the tests.
+
+Register the hypothesis 'exhaustive' profile to run 10 thousand examples.
+Run this profile with ``pytest --hypothesis-profile=exhaustive``
+"""
+
+from hypothesis import HealthCheck
+from hypothesis import settings
+
+settings.register_profile(
+ "exhaustive",
+ max_examples=10_000,
+ suppress_health_check=[HealthCheck.too_slow],
+)
+settings.register_profile(
+ "coverage",
+ max_examples=10,
+ suppress_health_check=[HealthCheck.too_slow],
+)
+settings.register_profile("ci", suppress_health_check=[HealthCheck.too_slow])
diff --git a/tests/containers_test.py b/tests/containers_test.py
index 645c79d3..c7890253 100644
--- a/tests/containers_test.py
+++ b/tests/containers_test.py
@@ -17,7 +17,12 @@
"""Test the kml classes."""
+import pytest
+
+from fastkml import containers
+from fastkml import features
from fastkml import kml
+from fastkml import styles
from tests.base import Lxml
from tests.base import StdLibrary
@@ -34,7 +39,7 @@ def test_document_boolean_visibility(self) -> None:
"""
- k = kml.KML.class_from_string(doc, strict=False)
+ k = kml.KML.from_string(doc, strict=False)
assert k.features[0].visibility
assert k.features[0].isopen
@@ -47,7 +52,7 @@ def test_document_boolean_open(self) -> None:
"""
- k = kml.KML.class_from_string(doc, strict=False)
+ k = kml.KML.from_string(doc, strict=False)
assert k.features[0].visibility == 0
assert k.features[0].isopen is False
@@ -60,11 +65,96 @@ def test_document_boolean_visibility_invalid(self) -> None:
"""
- d = kml.KML.class_from_string(doc, strict=False)
+ d = kml.KML.from_string(doc, strict=False)
assert d.features[0].visibility is None
assert d.features[0].isopen
+ def test_container_creation(self) -> None:
+ container = containers._Container(
+ ns="ns",
+ id="id",
+ target_id="target_id",
+ name="name",
+ )
+ assert container.ns == "ns"
+ assert container.name == "name"
+
+ def test_container_feature_append(self) -> None:
+ container = containers._Container(
+ ns="ns",
+ id="id",
+ target_id="target_id",
+ name="name",
+ )
+ feature = features._Feature(name="new_feature")
+ container.append(feature)
+ assert feature in container.features
+ with pytest.raises(ValueError, match="Cannot append self"):
+ container.append(container)
+
+ def test_document_container_get_style_url(self) -> None:
+ document = containers.Document(
+ name="Document",
+ ns="ns",
+ style_url=styles.StyleUrl(url="www.styleurl.com"),
+ )
+ assert document.get_style_by_url(style_url="www.styleurl.com") is None
+
+ def test_document_container_get_style_url_id(self) -> None:
+ style = styles.Style(id="style-0")
+ document = containers.Document(
+ name="Document",
+ ns="ns",
+ styles=[style],
+ )
+ assert document.get_style_by_url(style_url="#style-0") == style
+
+ def test_get_style_by_url(self) -> None:
+ doc = """
+
+ Document.kml
+ 1
+
+
+
+ normal
+ #normalState
+
+
+ highlight
+ #highlightState
+
+
+
+
+ """
+ k = kml.KML.from_string(doc)
+ assert len(k.features) == 1
+ document = k.features[0]
+
+ style0 = document.get_style_by_url(
+ "http://localhost:8080/somepath#exampleStyleDocument",
+ )
+ style1 = document.get_style_by_url("somepath#linestyleExample")
+ style2 = document.get_style_by_url("#styleMapExample")
+
+ assert isinstance(style0.styles[0], styles.LabelStyle)
+ assert style0.id == "exampleStyleDocument"
+ assert isinstance(style1.styles[0], styles.LineStyle)
+ assert style1.id == "linestyleExample"
+ assert isinstance(style2, styles.StyleMap)
+ assert style2.id == "styleMapExample"
+
class TestLxml(Lxml, TestStdLibrary):
"""Test with lxml."""
diff --git a/tests/data_test.py b/tests/data_test.py
index f820eb04..b142aa95 100644
--- a/tests/data_test.py
+++ b/tests/data_test.py
@@ -65,7 +65,7 @@ def test_schema_from_string(self) -> None:
"""
- s = kml.Schema.class_from_string(doc, ns=None)
+ s = kml.Schema.from_string(doc, ns=None)
assert len(s.fields) == 3
assert s.fields[0].type == DataType("string")
@@ -77,7 +77,7 @@ def test_schema_from_string(self) -> None:
assert s.fields[0].display_name == "Trail Head Name "
assert s.fields[1].display_name == "The length in miles "
assert s.fields[2].display_name == "change in altitude "
- s1 = kml.Schema.class_from_string(s.to_string(), ns=None)
+ s1 = kml.Schema.from_string(s.to_string(), ns=None)
assert len(s1.fields) == 3
assert s1.fields[0].type == DataType("string")
assert s1.fields[1].name == "TrailLength"
@@ -87,11 +87,11 @@ def test_schema_from_string(self) -> None:
''
f"{doc} "
)
- k = kml.KML.class_from_string(doc1, ns=None)
+ k = kml.KML.from_string(doc1, ns=None)
d = k.features[0]
s2 = d.schemata[0]
assert s.to_string() == s2.to_string()
- k1 = kml.KML.class_from_string(k.to_string())
+ k1 = kml.KML.from_string(k.to_string())
assert "Schema" in k1.to_string()
assert "SimpleField" in k1.to_string()
assert k1.to_string().replace("kml:", "").replace(
@@ -145,7 +145,7 @@ def test_untyped_extended_data(self) -> None:
assert len(p.extended_data.elements) == 2
k.append(p)
- k2 = kml.KML.class_from_string(k.to_string())
+ k2 = kml.KML.from_string(k.to_string())
extended_data = k2.features[0].extended_data
assert extended_data is not None
@@ -176,7 +176,7 @@ def test_untyped_extended_data_nested(self) -> None:
k.append(d)
d.append(f)
- k2 = kml.KML.class_from_string(k.to_string())
+ k2 = kml.KML.from_string(k.to_string())
document_data = k2.features[0].extended_data
folder_data = k2.features[0].features[0].extended_data
@@ -217,7 +217,7 @@ def test_extended_data(self) -> None:
"""
- k = kml.KML.class_from_string(doc)
+ k = kml.KML.from_string(doc)
extended_data = k.features[0].extended_data
@@ -248,7 +248,7 @@ def test_schema_data_from_str(self) -> None:
10
"""
- sd = data.SchemaData.class_from_string(doc)
+ sd = data.SchemaData.from_string(doc)
assert sd.schema_url == "#TrailHeadTypeId"
assert sd.data[0].name == "TrailHeadName"
assert sd.data[0].value == "Pi in the sky"
@@ -256,7 +256,7 @@ def test_schema_data_from_str(self) -> None:
assert sd.data[1].value == "3.14159"
assert sd.data[2].name == "ElevationGain"
assert sd.data[2].value == "10"
- sd1 = data.SchemaData.class_from_string(sd.to_string())
+ sd1 = data.SchemaData.from_string(sd.to_string())
assert sd1.schema_url == "#TrailHeadTypeId"
assert sd.to_string() == sd1.to_string()
@@ -268,12 +268,12 @@ def test_data_from_string(self) -> None:
1
"""
- d = data.Data.class_from_string(doc)
+ d = data.Data.from_string(doc)
assert d.name == "holeNumber"
assert d.value == "1"
assert isinstance(d.display_name, str)
assert "This is hole " in d.display_name
- d1 = data.Data.class_from_string(d.to_string())
+ d1 = data.Data.from_string(d.to_string())
assert d1.name == "holeNumber"
assert d.to_string() == d1.to_string()
diff --git a/tests/features_test.py b/tests/features_test.py
index 07d2690d..8384ae5d 100644
--- a/tests/features_test.py
+++ b/tests/features_test.py
@@ -71,11 +71,14 @@ def test_placemark_geometry_and_kml_geometry_parameter_set(self) -> None:
pt = geo.Point(10, 20)
point = geometry.Point(geometry=pt)
- with pytest.raises(ValueError):
+ with pytest.raises(
+ ValueError,
+ match="^You can only specify one of kml_geometry or geometry$",
+ ):
features.Placemark(geometry=pt, kml_geometry=point)
def test_network_link_with_link_parameter_only(self) -> None:
- """NetworkLink object with Link parameter only"""
+ """Test NetworkLink object with Link parameter only."""
network_link = features.NetworkLink(
link=links.Link(href="http://example.com/kml_file.kml"),
)
@@ -155,7 +158,7 @@ def test_network_link_read(self) -> None:
""
)
- network_link = features.NetworkLink.class_from_string(doc)
+ network_link = features.NetworkLink.from_string(doc)
assert network_link.name == "My NetworkLink"
assert network_link.visibility
@@ -170,6 +173,7 @@ def test_network_link_read(self) -> None:
assert network_link.address == "123 Main St"
assert network_link.phone_number == "555-1234"
assert network_link.snippet.text == "This is a snippet"
+ assert bool(network_link.snippet)
assert network_link.description == "This is a description"
assert network_link.view.latitude == 37.0
assert network_link.view.longitude == -122.0
diff --git a/tests/geometries/boundaries_test.py b/tests/geometries/boundaries_test.py
index 4b413f08..8e422a68 100644
--- a/tests/geometries/boundaries_test.py
+++ b/tests/geometries/boundaries_test.py
@@ -16,8 +16,14 @@
"""Test the Outer and Inner Boundary classes."""
+from typing import Type
+from typing import Union
+
import pygeoif.geometry as geo
+import pytest
+import fastkml
+from fastkml.exceptions import GeometryError
from fastkml.geometry import Coordinates
from fastkml.geometry import InnerBoundaryIs
from fastkml.geometry import LinearRing
@@ -35,7 +41,7 @@ def test_outer_boundary(self) -> None:
)
assert outer_boundary.geometry == geo.LinearRing(coords)
- assert outer_boundary.to_string(prettyprint=False).strip() == (
+ assert outer_boundary.to_string(prettyprint=False, precision=6).strip() == (
''
""
"1.000000,2.000000 2.000000,0.000000 0.000000,0.000000 1.000000,2.000000"
@@ -44,7 +50,7 @@ def test_outer_boundary(self) -> None:
def test_read_outer_boundary(self) -> None:
"""Test the from_string method."""
- outer_boundary = OuterBoundaryIs.class_from_string(
+ outer_boundary = OuterBoundaryIs.from_string(
''
""
"1.0,4.0 2.0,0.0 0.0,0.0 1.0,4.0 "
@@ -66,13 +72,34 @@ def test_inner_boundary(self) -> None:
assert inner_boundary.geometry == geo.LinearRing(coords)
assert bool(inner_boundary)
- assert inner_boundary.to_string(prettyprint=False).strip() == (
+ assert inner_boundary.to_string(prettyprint=False, precision=6).strip() == (
''
""
"1.000000,2.000000 2.000000,0.000000 0.000000,0.000000 1.000000,2.000000"
" "
)
+ def _test_boundary_geometry_error(
+ self,
+ boundary_class: Union[Type[InnerBoundaryIs], Type[OuterBoundaryIs]],
+ ) -> None:
+ p = geo.LinearRing(((1, 2), (2, 0)))
+ coords = ((1, 2), (2, 0), (0, 0), (1, 2))
+
+ with pytest.raises(GeometryError):
+ boundary_class(
+ kml_geometry=LinearRing(kml_coordinates=Coordinates(coords=coords)),
+ geometry=p,
+ )
+
+ def test_outer_boundary_geometry_error(self) -> None:
+ """Test that OuterBoundaryIs raises GeometryError with invalid geometry."""
+ self._test_boundary_geometry_error(OuterBoundaryIs)
+
+ def test_inner_boundary_geometry_error(self) -> None:
+ """Test that InnerBoundaryIs raises GeometryError with invalid geometry."""
+ self._test_boundary_geometry_error(InnerBoundaryIs)
+
def test_read_inner_boundary_multiple_linestrings(self) -> None:
"""
Test the from_string method.
@@ -80,7 +107,7 @@ def test_read_inner_boundary_multiple_linestrings(self) -> None:
When there are multiple LinearRings in the innerBoundaryIs element
only the first one is used.
"""
- inner_boundary = InnerBoundaryIs.class_from_string(
+ inner_boundary = InnerBoundaryIs.from_string(
''
""
"1.0,4.0 2.0,0.0 0.0,0.0 1.0,4.0 "
@@ -96,6 +123,22 @@ def test_read_inner_boundary_multiple_linestrings(self) -> None:
((1, 4), (2, 0), (0, 0), (1, 4)),
)
+ def test_inner_boundary_repr_roundtrip(self) -> None:
+ """Test that repr(obj) can be eval'd back to obj."""
+ coords = ((1, 2), (2, 0), (0, 0), (1, 2))
+ inner_boundary = InnerBoundaryIs(
+ kml_geometry=LinearRing(kml_coordinates=Coordinates(coords=coords)),
+ )
+
+ assert inner_boundary == eval( # noqa: S307
+ repr(inner_boundary),
+ {},
+ {
+ "fastkml": fastkml,
+ "LinearRing": geo.LinearRing,
+ },
+ )
+
class TestBoundariesLxml(Lxml, TestBoundaries):
pass
diff --git a/tests/geometries/coordinates_test.py b/tests/geometries/coordinates_test.py
index 9b8074e1..745186e2 100644
--- a/tests/geometries/coordinates_test.py
+++ b/tests/geometries/coordinates_test.py
@@ -29,7 +29,7 @@ def test_coordinates(self) -> None:
coordinates = Coordinates(coords=coords)
- assert coordinates.to_string().strip() == (
+ assert coordinates.to_string(precision=6).strip() == (
''
"0.000000,0.000000 0.000000,1.000000 1.000000,1.000000 "
"1.000000,0.000000 0.000000,0.000000"
@@ -38,7 +38,7 @@ def test_coordinates(self) -> None:
def test_coordinates_from_string(self) -> None:
"""Test the from_string method."""
- coordinates = Coordinates.class_from_string(
+ coordinates = Coordinates.from_string(
''
"0.000000,0.000000 1.000000,0.000000 1.0,1.0 0.000000,0.000000"
" ",
@@ -48,7 +48,7 @@ def test_coordinates_from_string(self) -> None:
def test_coordinates_from_string_with_whitespace(self) -> None:
"""Test the from_string method with whitespace."""
- coordinates = Coordinates.class_from_string(
+ coordinates = Coordinates.from_string(
'\n'
"-123.9404499372,49.169275246690,17 -123.940493701601,49.1694596207446,17 "
"-123.940356261489,49.16947180231761,17 -123.940306243,49.169291706171,17 "
diff --git a/tests/geometries/functions_test.py b/tests/geometries/functions_test.py
new file mode 100644
index 00000000..ae89da26
--- /dev/null
+++ b/tests/geometries/functions_test.py
@@ -0,0 +1,88 @@
+# Copyright (C) 2024 Rishit Chaudhary, Christian Ledermann
+#
+# This library is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This library is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+"""Test the geometry error handling."""
+from typing import Callable
+from unittest.mock import Mock
+from unittest.mock import patch
+
+import pytest
+
+from fastkml.enums import Verbosity
+from fastkml.exceptions import KMLParseError
+from fastkml.exceptions import KMLWriteError
+from fastkml.geometry import coordinates_subelement
+from fastkml.geometry import handle_invalid_geometry_error
+from tests.base import StdLibrary
+
+
+class TestGeometryFunctions(StdLibrary):
+ """Test functions in Geometry."""
+
+ @patch("fastkml.config.etree.tostring", return_value=b" ")
+ def test_handle_invalid_geometry_error_true(
+ self,
+ mock_to_string: Callable[..., str],
+ ) -> None:
+ mock_element = Mock()
+ with pytest.raises(
+ KMLParseError,
+ match=mock_to_string.return_value.decode(), # type: ignore[attr-defined]
+ ):
+ handle_invalid_geometry_error(
+ error=ValueError(),
+ element=mock_element,
+ strict=True,
+ )
+ mock_to_string.assert_called_once_with( # type: ignore[attr-defined]
+ mock_element,
+ encoding="UTF-8",
+ )
+
+ @patch("fastkml.config.etree.tostring", return_value=b" ")
+ def test_handle_invalid_geometry_error_false(
+ self,
+ mock_to_string: Callable[..., str],
+ ) -> None:
+ mock_element = Mock()
+ handle_invalid_geometry_error(
+ error=ValueError(),
+ element=mock_element,
+ strict=False,
+ )
+ mock_to_string.assert_called_once_with( # type: ignore[attr-defined]
+ mock_element,
+ encoding="UTF-8",
+ )
+
+ def test_coordinates_subelement_exception(self) -> None:
+ obj = Mock()
+ obj.coordinates = [(1.123456, 2.654321, 3.111111, 4.222222)]
+
+ element = Mock()
+
+ precision = 9
+ attr_name = "coordinates"
+
+ with pytest.raises(KMLWriteError):
+ coordinates_subelement(
+ obj=obj,
+ attr_name=attr_name,
+ node_name="",
+ element=element,
+ precision=precision,
+ verbosity=Verbosity.terse,
+ default=None,
+ )
diff --git a/tests/geometries/geometry_test.py b/tests/geometries/geometry_test.py
index 7e96699f..224b9a1c 100644
--- a/tests/geometries/geometry_test.py
+++ b/tests/geometries/geometry_test.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2021 - 2023 Christian Ledermann
+# Copyright (C) 2021 - 2024 Christian Ledermann
#
# This library is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
@@ -18,7 +18,6 @@
import pytest
from pygeoif import geometry as geo
-from fastkml import exceptions
from fastkml.enums import AltitudeMode
from fastkml.geometry import LinearRing
from fastkml.geometry import LineString
@@ -38,7 +37,7 @@ def test_altitude_mode(self) -> None:
clampToGround
"""
- g = Point.class_from_string(doc)
+ g = Point.from_string(doc)
assert g.altitude_mode == AltitudeMode("clampToGround")
@@ -50,7 +49,7 @@ def test_gx_altitude_mode(self) -> None:
"clampToSeaFloor "
""
)
- g = Point.class_from_string(doc)
+ g = Point.from_string(doc)
assert g.altitude_mode == AltitudeMode("clampToSeaFloor")
@@ -60,7 +59,7 @@ def test_extrude(self) -> None:
1
"""
- g = Point.class_from_string(doc)
+ g = Point.from_string(doc)
assert g.extrude is True
@@ -70,17 +69,18 @@ def test_tessellate(self) -> None:
1
"""
- g = Point.class_from_string(doc)
+ g = Point.from_string(doc)
- assert g.tessellate is True
+ assert not hasattr(g, "tessellate")
def test_point(self) -> None:
doc = """
0.000000,1.000000
"""
- g = Point.class_from_string(doc)
+ g = Point.from_string(doc)
+ assert g.geometry
assert g.geometry.__geo_interface__ == {
"type": "Point",
"bbox": (0.0, 1.0, 0.0, 1.0),
@@ -92,8 +92,9 @@ def test_linestring(self) -> None:
0.000000,0.000000 1.000000,1.000000
"""
- g = LineString.class_from_string(doc)
+ g = LineString.from_string(doc)
+ assert g.geometry
assert g.geometry.__geo_interface__ == {
"type": "LineString",
"bbox": (0.0, 0.0, 1.0, 1.0),
@@ -107,8 +108,9 @@ def test_linearring(self) -> None:
"""
- g = LinearRing.class_from_string(doc)
+ g = LinearRing.from_string(doc)
+ assert g.geometry
assert g.geometry.__geo_interface__ == {
"type": "LinearRing",
"bbox": (0.0, 0.0, 1.0, 1.0),
@@ -126,8 +128,9 @@ def test_polygon(self) -> None:
"""
- g = Polygon.class_from_string(doc)
+ g = Polygon.from_string(doc)
+ assert g.geometry
assert g.geometry.__geo_interface__ == {
"type": "Polygon",
"bbox": (0.0, 0.0, 1.0, 1.0),
@@ -151,8 +154,9 @@ def test_polygon_with_inner_boundary(self) -> None:
"""
- g = Polygon.class_from_string(doc)
+ g = Polygon.from_string(doc)
+ assert g.geometry
assert g.geometry.__geo_interface__ == {
"type": "Polygon",
"bbox": (-1.0, -1.0, 2.0, 2.0),
@@ -174,7 +178,7 @@ def test_multipoint(self) -> None:
"""
- g = MultiGeometry.class_from_string(doc)
+ g = MultiGeometry.from_string(doc)
assert len(g.geometry) == 2 # type: ignore[arg-type]
@@ -190,7 +194,7 @@ def test_multilinestring(self) -> None:
"""
- g = MultiGeometry.class_from_string(doc)
+ g = MultiGeometry.from_string(doc)
assert len(g.geometry) == 2 # type: ignore[arg-type]
@@ -222,7 +226,7 @@ def test_multipolygon(self) -> None:
"""
- g = MultiGeometry.class_from_string(doc)
+ g = MultiGeometry.from_string(doc)
assert g.geometry is not None
assert len(g.geometry) == 2
@@ -249,7 +253,7 @@ def test_geometrycollection(self) -> None:
"""
- g = MultiGeometry.class_from_string(doc)
+ g = MultiGeometry.from_string(doc)
assert len(g.geometry) == 4 # type: ignore[arg-type]
@@ -265,9 +269,10 @@ def test_geometrycollection_with_linearring(self) -> None:
"""
- g = MultiGeometry.class_from_string(doc)
+ g = MultiGeometry.from_string(doc)
- assert len(g.geometry) == 2 # type: ignore[arg-type]
+ assert g.geometry
+ assert len(g.geometry) == 2
assert g.geometry.geom_type == "GeometryCollection"
@@ -281,9 +286,7 @@ def test_init(self) -> None:
assert g.ns == "{http://www.opengis.net/kml/2.2}"
assert g.target_id == ""
assert g.id == ""
- assert g.extrude is None
assert g.altitude_mode is None
- assert g.tessellate is None
def test_init_with_args(self) -> None:
"""Test the init method with arguments."""
@@ -291,17 +294,13 @@ def test_init_with_args(self) -> None:
ns="",
target_id="target_id",
id="id",
- extrude=True,
altitude_mode=AltitudeMode.clamp_to_ground,
- tessellate=True,
)
assert g.ns == ""
assert g.target_id == "target_id"
assert g.id == "id"
- assert g.extrude is True
assert g.altitude_mode == AltitudeMode.clamp_to_ground
- assert g.tessellate is True
def test_to_string(self) -> None:
"""Test the to_string method."""
@@ -328,13 +327,13 @@ def test_to_string_with_args(self) -> None:
assert "http://www.opengis.net/kml/2.3" in g.to_string()
assert 'targetId="target_id"' in g.to_string()
assert 'id="my-id"' in g.to_string()
- assert "extrude>1" in g.to_string()
- assert "altitudeMode>relativeToGround<" in g.to_string()
- assert "tessellate>1<" in g.to_string()
+ assert "extrude" not in g.to_string()
+ assert "altitudeMode>relativeToGround<" not in g.to_string()
+ assert "tessellate" not in g.to_string()
def test_from_string(self) -> None:
"""Test the from_string method."""
- g = _Geometry.class_from_string(
+ g = _Geometry.from_string(
'<_Geometry id="my-id" targetId="target_id" '
'xmlns="http://www.opengis.net/kml/2.2">'
"1 "
@@ -346,61 +345,23 @@ def test_from_string(self) -> None:
assert g.ns == "{http://www.opengis.net/kml/2.2}"
assert g.target_id == "target_id"
assert g.id == "my-id"
- assert g.extrude is True
- assert g.altitude_mode == AltitudeMode.relative_to_ground
- assert g.tessellate is True
-
- def test_from_string_invalid_altitude_mode_strict(self) -> None:
- """Test the from_string method."""
- with pytest.raises(
- exceptions.KMLParseError,
- ):
- _Geometry.class_from_string(
- '<_Geometry id="my-id" targetId="target_id" '
- 'xmlns="http://www.opengis.net/kml/2.2">'
- "invalid "
- "",
- )
-
- def test_from_string_invalid_altitude_mode_relaxed(self) -> None:
- """Test the from_string method."""
- geom = _Geometry.class_from_string(
- '<_Geometry id="my-id" targetId="target_id" '
- 'xmlns="http://www.opengis.net/kml/2.2">'
- "invalid "
- "",
- strict=False,
- )
-
- assert geom.altitude_mode is None
-
- def test_from_string_invalid_extrude(self) -> None:
- """Test the from_string method."""
- with pytest.raises(
- exceptions.KMLParseError,
- ):
- _Geometry.class_from_string(
- '<_Geometry id="my-id" targetId="target_id" '
- 'xmlns="http://www.opengis.net/kml/2.2">'
- "invalid "
- "",
- )
+ assert g.altitude_mode is None
+ assert not hasattr(g, "tessellate")
+ assert not hasattr(g, "extrude")
def test_from_minimal_string(self) -> None:
- g = _Geometry.class_from_string(
+ g = _Geometry.from_string(
'<_Geometry xmlns="http://www.opengis.net/kml/2.2/" />',
)
assert g.ns == "{http://www.opengis.net/kml/2.2}"
assert g.target_id == ""
assert g.id == ""
- assert g.extrude is None
assert g.altitude_mode is None
- assert g.tessellate is None
def test_from_string_omitting_ns(self) -> None:
"""Test the from_string method."""
- g = _Geometry.class_from_string(
+ g = _Geometry.from_string(
''
"1 "
@@ -412,9 +373,9 @@ def test_from_string_omitting_ns(self) -> None:
assert g.ns == "{http://www.opengis.net/kml/2.2}"
assert g.target_id == "target_id"
assert g.id == "my-id"
- assert g.extrude is True
- assert g.altitude_mode == AltitudeMode.relative_to_ground
- assert g.tessellate is True
+ assert g.altitude_mode is None
+ assert not hasattr(g, "tessellate")
+ assert not hasattr(g, "extrude")
class TestCreateKmlGeometry(StdLibrary):
@@ -423,32 +384,37 @@ def test_create_kml_geometry_point(self) -> None:
g = create_kml_geometry(geo.Point(0, 1))
assert isinstance(g, Point)
+ assert g.geometry
assert g.geometry.__geo_interface__ == {
"type": "Point",
"bbox": (0.0, 1.0, 0.0, 1.0),
"coordinates": (0.0, 1.0),
}
assert "Point>" in g.to_string()
- assert "coordinates>0.000000,1.000000" in g.to_string()
+ assert "coordinates>0.000000,1.000000" in g.to_string(precision=6)
def test_create_kml_geometry_linestring(self) -> None:
"""Test the create_kml_geometry function."""
g = create_kml_geometry(geo.LineString([(0, 0), (1, 1)]))
assert isinstance(g, LineString)
+ assert g.geometry
assert g.geometry.__geo_interface__ == {
"type": "LineString",
"bbox": (0.0, 0.0, 1.0, 1.0),
"coordinates": ((0.0, 0.0), (1.0, 1.0)),
}
assert "LineString>" in g.to_string()
- assert "coordinates>0.000000,0.000000 1.000000,1.000000" in g.to_string()
+ assert "coordinates>0.000000,0.000000 1.000000,1.000000" in g.to_string(
+ precision=6,
+ )
def test_create_kml_geometry_linearring(self) -> None:
"""Test the create_kml_geometry function."""
g = create_kml_geometry(geo.LinearRing([(0, 0), (1, 1), (1, 0), (0, 0)]))
assert isinstance(g, LinearRing)
+ assert g.geometry
assert g.geometry.__geo_interface__ == {
"type": "LinearRing",
"bbox": (0.0, 0.0, 1.0, 1.0),
@@ -458,14 +424,14 @@ def test_create_kml_geometry_linearring(self) -> None:
assert (
"coordinates>0.000000,0.000000 1.000000,1.000000 1.000000,0.000000 "
"0.000000,0.000000"
- ) in g.to_string()
+ ) in g.to_string(precision=6)
def test_create_kml_geometry_polygon(self) -> None:
"""Test the create_kml_geometry function."""
g = create_kml_geometry(geo.Polygon([(0, 0), (1, 1), (1, 0), (0, 0)]))
assert isinstance(g, Polygon)
- assert g.geometry is not None
+ assert g.geometry
assert g.geometry.__geo_interface__ == {
"type": "Polygon",
"bbox": (0.0, 0.0, 1.0, 1.0),
@@ -475,21 +441,22 @@ def test_create_kml_geometry_polygon(self) -> None:
assert (
"coordinates>0.000000,0.000000 1.000000,1.000000 1.000000,0.000000 "
"0.000000,0.000000"
- ) in g.to_string()
+ ) in g.to_string(precision=6)
def test_create_kml_geometry_multipoint(self) -> None:
"""Test the create_kml_geometry function."""
g = create_kml_geometry(geo.MultiPoint([(0, 0), (1, 1), (1, 0), (2, 2)]))
assert isinstance(g, MultiGeometry)
- assert g.geometry is not None
+ assert g.geometry
assert len(g.geometry) == 4
- assert "MultiGeometry>" in g.to_string()
- assert "Point>" in g.to_string()
- assert "coordinates>0.000000,0.000000" in g.to_string()
- assert "coordinates>1.000000,1.000000" in g.to_string()
- assert "coordinates>1.000000,0.000000" in g.to_string()
- assert "coordinates>2.000000,2.000000" in g.to_string()
+ xml = g.to_string(precision=6)
+ assert "MultiGeometry>" in xml
+ assert "Point>" in xml
+ assert "coordinates>0.000000,0.000000" in xml
+ assert "coordinates>1.000000,1.000000" in xml
+ assert "coordinates>1.000000,0.000000" in xml
+ assert "coordinates>2.000000,2.000000" in xml
def test_create_kml_geometry_multilinestring(self) -> None:
"""Test the create_kml_geometry function."""
@@ -498,12 +465,13 @@ def test_create_kml_geometry_multilinestring(self) -> None:
)
assert isinstance(g, MultiGeometry)
- assert g.geometry is not None
+ assert g.geometry
assert len(g.geometry) == 2
- assert "MultiGeometry>" in g.to_string()
- assert "LineString>" in g.to_string()
- assert "coordinates>0.000000,0.000000 1.000000,1.000000" in g.to_string()
- assert "coordinates>0.000000,0.000000 1.000000,1.000000" in g.to_string()
+ xml = g.to_string(precision=6)
+ assert "MultiGeometry>" in xml
+ assert "LineString>" in xml
+ assert "coordinates>0.000000,0.000000 1.000000,1.000000" in xml
+ assert "coordinates>0.000000,0.000000 1.000000,1.000000" in xml
def test_create_kml_geometry_multipolygon(self) -> None:
"""Test the create_kml_geometry function."""
@@ -520,22 +488,23 @@ def test_create_kml_geometry_multipolygon(self) -> None:
)
assert isinstance(g, MultiGeometry)
- assert g.geometry is not None
+ assert g.geometry
assert len(g.geometry) == 2
- assert "MultiGeometry>" in g.to_string()
- assert "Polygon>" in g.to_string()
+ xml = g.to_string(precision=6)
+ assert "MultiGeometry>" in xml
+ assert "Polygon>" in xml
assert (
"coordinates>0.000000,0.000000 0.000000,1.000000 1.000000,1.000000 "
"1.000000,0.000000 0.000000,0.000000"
- ) in g.to_string()
+ ) in xml
assert (
"coordinates>0.100000,0.100000 0.100000,0.200000 0.200000,0.200000 "
"0.200000,0.100000 0.100000,0.100000"
- ) in g.to_string()
+ ) in xml
assert (
"coordinates>0.000000,0.000000 0.000000,1.000000 1.000000,1.000000 "
"1.000000,0.000000 0.000000,0.000000"
- ) in g.to_string()
+ ) in xml
def test_create_kml_geometry_geometrycollection(self) -> None:
multipoint = geo.MultiPoint([(0, 0), (1, 1), (1, 2), (2, 2)])
@@ -555,16 +524,17 @@ def test_create_kml_geometry_geometrycollection(self) -> None:
g = create_kml_geometry(gc)
assert isinstance(g, MultiGeometry)
- assert g.geometry is not None
+ assert g.geometry
assert len(g.geometry) == 7
- assert "MultiGeometry>" in g.to_string()
- assert "LineString>" in g.to_string()
- assert "LinearRing>" in g.to_string()
- assert "Polygon>" in g.to_string()
- assert "outerBoundaryIs>" in g.to_string()
- assert "innerBoundaryIs>" in g.to_string()
- assert "Point>" in g.to_string()
- assert "coordinates>0.000000,0.000000" in g.to_string()
+ xml = g.to_string(precision=6)
+ assert "MultiGeometry>" in xml
+ assert "LineString>" in xml
+ assert "LinearRing>" in xml
+ assert "Polygon>" in xml
+ assert "outerBoundaryIs>" in xml
+ assert "innerBoundaryIs>" in xml
+ assert "Point>" in xml
+ assert "coordinates>0.000000,0.000000" in xml
assert g.geometry == gc
def test_create_kml_geometry_geometrycollection_roundtrip(self) -> None:
@@ -593,7 +563,7 @@ def test_create_kml_geometry_geometrycollection_roundtrip(self) -> None:
)
g = create_kml_geometry(gc)
- mg = MultiGeometry.class_from_string(g.to_string())
+ mg = MultiGeometry.from_string(g.to_string())
assert mg.geometry == gc
diff --git a/tests/geometries/linearring_test.py b/tests/geometries/linearring_test.py
index bd3ff0a1..f6a91057 100644
--- a/tests/geometries/linearring_test.py
+++ b/tests/geometries/linearring_test.py
@@ -45,12 +45,13 @@ def test_to_string(self) -> None:
assert "LinearRing" in linear_ring.to_string()
assert (
"coordinates>0.000000,0.000000 0.000000,1.000000 1.000000,1.000000 "
- "1.000000,0.000000 0.000000,0.000000" in linear_ring.to_string()
+ "1.000000,0.000000 0.000000,0.000000"
+ in linear_ring.to_string(precision=6)
)
def test_from_string(self) -> None:
"""Test the from_string method."""
- linear_ring = LinearRing.class_from_string(
+ linear_ring = LinearRing.from_string(
''
"0.000000,0.000000 1.000000,0.000000 1.0,1.0 "
"0.000000,0.000000 "
@@ -61,7 +62,7 @@ def test_from_string(self) -> None:
def test_empty_from_string(self) -> None:
"""Test the from_string method with an empty LinearRing."""
- linear_ring = LinearRing.class_from_string(
+ linear_ring = LinearRing.from_string(
''
" "
" ",
@@ -71,7 +72,7 @@ def test_empty_from_string(self) -> None:
def test_no_coordinates_from_string(self) -> None:
"""Test the from_string method with an empty LinearRing."""
- linear_ring = LinearRing.class_from_string(
+ linear_ring = LinearRing.from_string(
''
" ",
)
@@ -84,7 +85,7 @@ def test_from_string_invalid_coordinates_non_numerical(self) -> None:
KMLParseError,
match=r"^Invalid coordinates in",
):
- LinearRing.class_from_string(
+ LinearRing.from_string(
''
"0.000000,0.000000 1.000000,0.000000 1.0,1.0 "
"0.000000,0.000000 1.000000,a "
@@ -93,7 +94,7 @@ def test_from_string_invalid_coordinates_non_numerical(self) -> None:
def test_mixed_2d_3d_coordinates_from_string_relaxed(self) -> None:
"""Test the from_string method with mixed 2D and 3D coordinates."""
- linear_ring = LinearRing.class_from_string(
+ linear_ring = LinearRing.from_string(
''
"0.000000,0.000000 1.000000,0.000000 1.0,1.0 "
"0.000000,0.000000 1.000000,2.000000,3.000000 "
diff --git a/tests/geometries/linestring_test.py b/tests/geometries/linestring_test.py
index bfc773f8..13e60b76 100644
--- a/tests/geometries/linestring_test.py
+++ b/tests/geometries/linestring_test.py
@@ -19,7 +19,11 @@
import pygeoif.geometry as geo
import pytest
+from fastkml import exceptions
+from fastkml.enums import Verbosity
+from fastkml.exceptions import GeometryError
from fastkml.exceptions import KMLParseError
+from fastkml.geometry import Coordinates
from fastkml.geometry import LineString
from tests.base import Lxml
from tests.base import StdLibrary
@@ -36,6 +40,14 @@ def test_init(self) -> None:
assert line_string.altitude_mode is None
assert line_string.extrude is None
+ def test_geometry_error(self) -> None:
+ """Test GeometryError."""
+ p = geo.LineString(((1, 2), (2, 0)))
+ q = Coordinates(ns="ns")
+
+ with pytest.raises(GeometryError):
+ LineString(geometry=p, kml_coordinates=q)
+
def test_to_string(self) -> None:
"""Test the to_string method."""
ls = geo.LineString(((1, 2), (2, 0)))
@@ -45,12 +57,12 @@ def test_to_string(self) -> None:
assert "LineString" in line_string.to_string()
assert (
"coordinates>1.000000,2.000000 2.000000,0.000000"
- in line_string.to_string()
+ in line_string.to_string(precision=6)
)
def test_from_string(self) -> None:
"""Test the from_string method."""
- linestring = LineString.class_from_string(
+ linestring = LineString.from_string(
''
"1 "
"1 "
@@ -65,8 +77,7 @@ def test_from_string(self) -> None:
)
def test_mixed_2d_3d_coordinates_from_string(self) -> None:
-
- linestring = LineString.class_from_string(
+ linestring = LineString.from_string(
''
"1 "
"1 "
@@ -79,7 +90,7 @@ def test_mixed_2d_3d_coordinates_from_string(self) -> None:
assert not linestring
def test_mixed_2d_3d_coordinates_from_string_relaxed(self) -> None:
- line_string = LineString.class_from_string(
+ line_string = LineString.from_string(
''
"1 "
"1 "
@@ -94,7 +105,7 @@ def test_mixed_2d_3d_coordinates_from_string_relaxed(self) -> None:
def test_empty_from_string(self) -> None:
"""Test the from_string method with an empty LineString."""
- linestring = LineString.class_from_string(
+ linestring = LineString.from_string(
''
"1 "
"1 "
@@ -107,7 +118,7 @@ def test_empty_from_string(self) -> None:
def test_no_coordinates_from_string(self) -> None:
"""Test the from_string method with no coordinates."""
- linestring = LineString.class_from_string(
+ linestring = LineString.from_string(
''
"1 "
"1 "
@@ -122,7 +133,7 @@ def test_from_string_invalid_coordinates_non_numerical(self) -> None:
KMLParseError,
match=r"^Invalid coordinates in",
):
- LineString.class_from_string(
+ LineString.from_string(
''
"1 "
"1 "
@@ -133,7 +144,7 @@ def test_from_string_invalid_coordinates_non_numerical(self) -> None:
)
def test_from_string_invalid_coordinates_nan(self) -> None:
- line_string = LineString.class_from_string(
+ line_string = LineString.from_string(
''
"false "
"true "
@@ -145,9 +156,79 @@ def test_from_string_invalid_coordinates_nan(self) -> None:
" ",
)
+ assert line_string.geometry
assert len(line_string.geometry.coords) == 5
assert line_string.to_string()
+ def test_from_string_invalid_extrude(self) -> None:
+ """Test the from_string method."""
+ with pytest.raises(
+ exceptions.KMLParseError,
+ ):
+ LineString.from_string(
+ ''
+ "invalid "
+ " ",
+ )
+
+ def test_from_string_invalid_tessellate(self) -> None:
+ """Test the from_string method."""
+ with pytest.raises(
+ exceptions.KMLParseError,
+ ):
+ LineString.from_string(
+ ''
+ "invalid "
+ " ",
+ )
+
+ def test_to_string_terse_default(self) -> None:
+ ls = geo.LineString(((1, 2), (2, 0)))
+ line_string = LineString(geometry=ls, extrude=False, tessellate=False)
+
+ xml = line_string.to_string(verbosity=Verbosity.terse)
+
+ assert "tessellate" not in xml
+ assert "extrude" not in xml
+
+ def test_to_string_terse(self) -> None:
+ ls = geo.LineString(((1, 2), (2, 0)))
+ line_string = LineString(geometry=ls, extrude=True, tessellate=True)
+
+ xml = line_string.to_string(verbosity=Verbosity.terse)
+
+ assert "tessellate>1" in xml
+ assert "extrude>1" in xml
+
+ def test_to_string_verbose_default(self) -> None:
+ ls = geo.LineString(((1, 2), (2, 0)))
+ line_string = LineString(geometry=ls, extrude=False, tessellate=False)
+
+ xml = line_string.to_string(verbosity=Verbosity.verbose)
+
+ assert "tessellate>0" in xml
+ assert "extrude>0" in xml
+
+ def test_to_string_verbose(self) -> None:
+ ls = geo.LineString(((1, 2), (2, 0)))
+ line_string = LineString(geometry=ls, extrude=True, tessellate=True)
+
+ xml = line_string.to_string(verbosity=Verbosity.verbose)
+
+ assert "tessellate>1" in xml
+ assert "extrude>1" in xml
+
+ def test_to_string_verbose_none(self) -> None:
+ ls = geo.LineString(((1, 2), (2, 0)))
+ line_string = LineString(geometry=ls)
+
+ xml = line_string.to_string(verbosity=Verbosity.verbose)
+
+ assert "tessellate>0" in xml
+ assert "extrude>0" in xml
+
class TestLineStringLxml(Lxml, TestLineString):
"""Test with lxml."""
diff --git a/tests/geometries/multigeometry_test.py b/tests/geometries/multigeometry_test.py
index 2ae32abe..bdcc182c 100644
--- a/tests/geometries/multigeometry_test.py
+++ b/tests/geometries/multigeometry_test.py
@@ -16,7 +16,10 @@
"""Test the geometry classes."""
import pygeoif.geometry as geo
+import pytest
+from fastkml.enums import Verbosity
+from fastkml.exceptions import GeometryError
from fastkml.geometry import MultiGeometry
from tests.base import Lxml
from tests.base import StdLibrary
@@ -31,7 +34,7 @@ def test_1_point(self) -> None:
mg = MultiGeometry(geometry=p)
- assert "coordinates>1.000000,2.000000" in mg.to_string()
+ assert "coordinates>1.000000,2.000000" in mg.to_string(precision=6)
assert "MultiGeometry>" in mg.to_string()
assert "Point>" in mg.to_string()
@@ -41,8 +44,8 @@ def test_2_points(self) -> None:
mg = MultiGeometry(geometry=p)
- assert "coordinates>1.000000,2.000000" in mg.to_string()
- assert "coordinates>3.000000,4.000000" in mg.to_string()
+ assert "coordinates>1.000000,2.000000" in mg.to_string(precision=6)
+ assert "coordinates>3.000000,4.000000" in mg.to_string(precision=6)
assert "MultiGeometry>" in mg.to_string()
assert "Point>" in mg.to_string()
@@ -55,7 +58,7 @@ def test_2_points_read(self) -> None:
" "
)
- mg = MultiGeometry.class_from_string(xml)
+ mg = MultiGeometry.from_string(xml)
assert mg.geometry == geo.MultiPoint([(1, 2), (3, 4)])
@@ -67,7 +70,9 @@ def test_1_linestring(self) -> None:
mg = MultiGeometry(geometry=p)
- assert "coordinates>1.000000,2.000000 3.000000,4.000000" in mg.to_string()
+ assert "coordinates>1.000000,2.000000 3.000000,4.000000" in mg.to_string(
+ precision=6,
+ )
assert "MultiGeometry>" in mg.to_string()
assert "LineString>" in mg.to_string()
@@ -77,8 +82,12 @@ def test_2_linestrings(self) -> None:
mg = MultiGeometry(geometry=p)
- assert "coordinates>1.000000,2.000000 3.000000,4.000000" in mg.to_string()
- assert "coordinates>5.000000,6.000000 7.000000,8.000000" in mg.to_string()
+ assert "coordinates>1.000000,2.000000 3.000000,4.000000" in mg.to_string(
+ precision=6,
+ )
+ assert "coordinates>5.000000,6.000000 7.000000,8.000000" in mg.to_string(
+ precision=6,
+ )
assert "MultiGeometry>" in mg.to_string()
assert "LineString>" in mg.to_string()
@@ -92,7 +101,7 @@ def test_2_linestrings_read(self) -> None:
""
)
- mg = MultiGeometry.class_from_string(xml, ns="")
+ mg = MultiGeometry.from_string(xml, ns="")
assert mg.geometry == geo.MultiLineString([[(1, 2), (3, 4)], [(5, 6), (7, 8)]])
@@ -108,7 +117,7 @@ def test_1_polygon(self) -> None:
assert (
"coordinates>1.000000,2.000000 3.000000,4.000000 5.000000,6.000000 "
- "1.000000,2.000000" in mg.to_string()
+ "1.000000,2.000000" in mg.to_string(precision=6)
)
assert "MultiGeometry>" in mg.to_string()
assert "Polygon>" in mg.to_string()
@@ -129,11 +138,11 @@ def test_1_polygons_with_holes(self) -> None:
assert (
"coordinates>0.000000,0.000000 0.000000,1.000000 1.000000,1.000000 "
- "1.000000,0.000000 0.000000,0.000000" in mg.to_string()
+ "1.000000,0.000000 0.000000,0.000000" in mg.to_string(precision=6)
)
assert (
"coordinates>0.250000,0.250000 0.250000,0.500000 0.500000,0.500000 "
- "0.500000,0.250000 0.250000,0.250000" in mg.to_string()
+ "0.500000,0.250000 0.250000,0.250000" in mg.to_string(precision=6)
)
assert "MultiGeometry>" in mg.to_string()
assert "Polygon>" in mg.to_string()
@@ -156,15 +165,15 @@ def test_2_polygons(self) -> None:
assert (
"coordinates>0.000000,0.000000 0.000000,1.000000 1.000000,1.000000 "
- "1.000000,0.000000 0.000000,0.000000" in mg.to_string()
+ "1.000000,0.000000 0.000000,0.000000" in mg.to_string(precision=6)
)
assert (
"coordinates>0.100000,0.100000 0.100000,0.200000 0.200000,0.200000 "
- "0.200000,0.100000 0.100000,0.100000" in mg.to_string()
+ "0.200000,0.100000 0.100000,0.100000" in mg.to_string(precision=6)
)
assert (
"coordinates>0.000000,0.000000 0.000000,2.000000 1.000000,1.000000 "
- "1.000000,0.000000 0.000000,0.000000" in mg.to_string()
+ "1.000000,0.000000 0.000000,0.000000" in mg.to_string(precision=6)
)
assert "MultiGeometry>" in mg.to_string()
assert "Polygon>" in mg.to_string()
@@ -189,7 +198,7 @@ def test_2_polygons_read(self) -> None:
""
)
- mg = MultiGeometry.class_from_string(xml)
+ mg = MultiGeometry.from_string(xml)
assert mg.geometry == geo.MultiPolygon(
[
@@ -211,7 +220,7 @@ def test_1_point(self) -> None:
mg = MultiGeometry(geometry=p)
- assert "coordinates>1.000000,2.000000" in mg.to_string()
+ assert "coordinates>1.000000,2.000000" in mg.to_string(precision=6)
assert "MultiGeometry>" in mg.to_string()
assert "Point>" in mg.to_string()
@@ -261,6 +270,41 @@ def test_multi_geometries(self) -> None:
assert "Polygon>" in mg.to_string()
assert "MultiGeometry>" in mg.to_string()
+ def test_multi_geometries_verbose(self) -> None:
+ p = geo.Point(1, 2)
+ ls = geo.LineString(((1, 2), (2, 0)))
+ lr = geo.LinearRing(((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)))
+ poly = geo.Polygon(
+ [(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)],
+ [[(0.1, 0.1), (0.1, 0.9), (0.9, 0.9), (0.9, 0.1), (0.1, 0.1)]],
+ )
+ gc = geo.GeometryCollection([p, ls, lr, poly])
+ mp = geo.MultiPolygon(
+ [
+ (
+ ((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)),
+ [((0.1, 0.1), (0.1, 0.2), (0.2, 0.2), (0.2, 0.1))],
+ ),
+ (((0.0, 0.0), (0.0, 2.0), (1.0, 1.0), (1.0, 0.0)),),
+ ],
+ )
+ ml = geo.MultiLineString([[(1, 2), (3, 4)], [(5, 6), (7, 8)]])
+ mgc = geo.GeometryCollection([gc, mp, ml])
+ mg = MultiGeometry(ns="", geometry=mgc)
+
+ xml = mg.to_string(verbosity=Verbosity.verbose)
+ assert xml.count("tessellate>0<") == 12 # points do not have tessellate
+ assert xml.count("extrude>0<") == 13
+ assert xml.count("altitudeMode") == 26
+ assert xml.count(">clampToGround<") == 13
+
+ def test_geometry_error(self) -> None:
+ """Test GeometryError."""
+ p = geo.MultiPoint(((1.0, 2.0),))
+
+ with pytest.raises(GeometryError):
+ MultiGeometry(geometry=p, kml_geometries=(MultiGeometry(geometry=p),))
+
def test_multi_geometries_read(self) -> None:
xml = (
''
@@ -292,7 +336,7 @@ def test_multi_geometries_read(self) -> None:
" "
)
- mg = MultiGeometry.class_from_string(xml)
+ mg = MultiGeometry.from_string(xml)
assert mg.geometry == geo.GeometryCollection(
(
@@ -365,13 +409,13 @@ def test_empty_multi_geometries_read(self) -> None:
" "
)
- mg = MultiGeometry.class_from_string(xml)
+ mg = MultiGeometry.from_string(xml)
assert mg.geometry is None
- assert "MultiGeometry>" in mg.to_string()
- assert "coordinates>" not in mg.to_string()
- assert mg.extrude is False
- assert mg.tessellate is False
+ assert "MultiGeometry" in mg.to_string()
+ assert "coordinates" not in mg.to_string()
+ assert not hasattr(mg, "extrude")
+ assert not hasattr(mg, "tessellate")
class TestMultiPointLxml(Lxml, TestMultiPointStdLibrary):
diff --git a/tests/geometries/point_test.py b/tests/geometries/point_test.py
index d0389647..32fb08df 100644
--- a/tests/geometries/point_test.py
+++ b/tests/geometries/point_test.py
@@ -19,7 +19,10 @@
import pygeoif.geometry as geo
import pytest
+from fastkml.enums import Verbosity
+from fastkml.exceptions import GeometryError
from fastkml.exceptions import KMLParseError
+from fastkml.geometry import Coordinates
from fastkml.geometry import Point
from tests.base import Lxml
from tests.base import StdLibrary
@@ -38,6 +41,14 @@ def test_init(self) -> None:
assert point.altitude_mode is None
assert point.extrude is None
+ def test_geometry_error(self) -> None:
+ """Test GeometryError."""
+ p = geo.Point(1, 2)
+ q = Coordinates(ns="ns")
+
+ with pytest.raises(GeometryError):
+ Point(geometry=p, kml_coordinates=q)
+
def test_to_string_2d(self) -> None:
"""Test the to_string method."""
p = geo.Point(1, 2)
@@ -45,7 +56,7 @@ def test_to_string_2d(self) -> None:
point = Point(geometry=p)
assert "Point" in point.to_string()
- assert "coordinates>1.000000,2.000000" in point.to_string()
+ assert "coordinates>1.000000,2.000000" in point.to_string(precision=6)
def test_to_string_3d(self) -> None:
"""Test the to_string method."""
@@ -54,7 +65,54 @@ def test_to_string_3d(self) -> None:
point = Point(geometry=p)
assert "Point" in point.to_string()
- assert "coordinates>1.000000,2.000000,3.000000" in point.to_string()
+ assert "coordinates>1.000000,2.000000,3.000000" in point.to_string(
+ precision=6,
+ )
+
+ def test_to_string_terse_default(self) -> None:
+ """Test the to_string method, exclude default for extrude in terse mode."""
+ p = geo.Point(1, 2)
+
+ point = Point(geometry=p, extrude=False)
+
+ assert "coordinates>" in point.to_string(verbosity=Verbosity.terse)
+ assert "extrude" not in point.to_string(verbosity=Verbosity.terse)
+
+ def test_to_string_terse_non_default(self) -> None:
+ """Test the to_string method, include extrude when true in terse mode."""
+ p = geo.Point(1, 2)
+
+ point = Point(geometry=p, extrude=True)
+
+ assert "coordinates>" in point.to_string(verbosity=Verbosity.terse)
+ assert "extrude>1" in point.to_string(verbosity=Verbosity.terse)
+
+ def test_to_string_verbose_default(self) -> None:
+ """Test the to_string method, include default for extrude in verbose mode."""
+ p = geo.Point(1, 2)
+
+ point = Point(geometry=p, extrude=False)
+
+ assert "coordinates>" in point.to_string(verbosity=Verbosity.verbose)
+ assert "extrude>0" in point.to_string(verbosity=Verbosity.verbose)
+
+ def test_to_string_verbose_non_default(self) -> None:
+ """Test the to_string method, include extrude when true in verbose mode."""
+ p = geo.Point(1, 2)
+
+ point = Point(geometry=p, extrude=True)
+
+ assert "coordinates>" in point.to_string(verbosity=Verbosity.verbose)
+ assert "extrude>1" in point.to_string(verbosity=Verbosity.verbose)
+
+ def test_to_string_verbose_none(self) -> None:
+ """Test the to_string method, include extrude when true in verbose mode."""
+ p = geo.Point(1, 2)
+
+ point = Point(geometry=p, extrude=False)
+
+ assert "coordinates>" in point.to_string(verbosity=Verbosity.verbose)
+ assert "extrude>0" in point.to_string(verbosity=Verbosity.verbose)
def test_to_string_2d_precision_0(self) -> None:
"""Test the to_string method."""
@@ -101,7 +159,7 @@ def test_to_string_empty_geometry(self) -> None:
def test_from_string_2d(self) -> None:
"""Test the from_string method for a 2 dimensional point."""
- point = Point.class_from_string(
+ point = Point.from_string(
''
"1.000000,2.000000 "
" ",
@@ -110,11 +168,10 @@ def test_from_string_2d(self) -> None:
assert point.geometry == geo.Point(1, 2)
assert point.altitude_mode is None
assert point.extrude is None
- assert point.tessellate is None
def test_from_string_uppercase_altitude_mode_relaxed(self) -> None:
"""Test the from_string method for an uppercase altitude mode."""
- point = Point.class_from_string(
+ point = Point.from_string(
''
"RELATIVETOGROUND "
"1.000000,2.000000 "
@@ -123,6 +180,7 @@ def test_from_string_uppercase_altitude_mode_relaxed(self) -> None:
)
assert point.geometry == geo.Point(1, 2)
+ assert point.altitude_mode
assert point.altitude_mode.value == "relativeToGround"
def test_from_string_uppercase_altitude_mode_strict(self) -> None:
@@ -131,16 +189,40 @@ def test_from_string_uppercase_altitude_mode_strict(self) -> None:
KMLParseError,
match=r"Value RELATIVETOGROUND is not a valid value for Enum AltitudeMode$",
):
- assert Point.class_from_string(
+ assert Point.from_string(
''
"RELATIVETOGROUND "
"1.000000,2.000000 "
" ",
)
+ def test_from_string_invalid_altitude_mode_strict(self) -> None:
+ with pytest.raises(
+ KMLParseError,
+ match=r"^Error parsing '<",
+ ):
+ assert Point.from_string(
+ ''
+ "INVALID "
+ "1.000000,2.000000 "
+ " ",
+ )
+
+ def test_from_string_invalid_altitude_mode_relaxed(self) -> None:
+ point = Point.from_string(
+ ''
+ "invalid "
+ "1.000000,2.000000 "
+ " ",
+ strict=False,
+ )
+
+ assert point.geometry == geo.Point(1, 2)
+ assert not point.altitude_mode
+
def test_from_string_3d(self) -> None:
"""Test the from_string method for a 3 dimensional point."""
- point = Point.class_from_string(
+ point = Point.from_string(
''
"1 "
"1 "
@@ -150,13 +232,13 @@ def test_from_string_3d(self) -> None:
)
assert point.geometry == geo.Point(1, 2, 3)
+ assert point.altitude_mode
assert point.altitude_mode.value == "absolute"
assert point.extrude
- assert point.tessellate
def test_empty_from_string(self) -> None:
"""Test the from_string method."""
- point = Point.class_from_string(
+ point = Point.from_string(
" ",
ns="",
)
@@ -165,7 +247,7 @@ def test_empty_from_string(self) -> None:
def test_empty_from_string_relaxed(self) -> None:
"""Test that no error is raised when the geometry is empty and not strict."""
- point = Point.class_from_string(
+ point = Point.from_string(
" ",
ns="",
strict=False,
@@ -174,7 +256,7 @@ def test_empty_from_string_relaxed(self) -> None:
assert point.geometry is None
def test_from_string_empty_coordinates(self) -> None:
- point = Point.class_from_string(
+ point = Point.from_string(
' ',
)
@@ -182,8 +264,7 @@ def test_from_string_empty_coordinates(self) -> None:
assert point.geometry is None
def test_from_string_invalid_coordinates(self) -> None:
-
- point = Point.class_from_string(
+ point = Point.from_string(
''
"1 ",
)
@@ -191,8 +272,7 @@ def test_from_string_invalid_coordinates(self) -> None:
assert not point
def test_from_string_invalid_coordinates_4d(self) -> None:
-
- point = Point.class_from_string(
+ point = Point.from_string(
''
"1,2,3,4 ",
)
@@ -203,21 +283,11 @@ def test_from_string_invalid_coordinates_non_numerical(self) -> None:
KMLParseError,
match=r"^Invalid coordinates in",
):
- Point.class_from_string(
+ Point.from_string(
''
"a,b,c ",
)
- def test_from_string_invalid_coordinates_nan(self) -> None:
- with pytest.raises(
- KMLParseError,
- match=r"^Invalid coordinates in",
- ):
- Point.class_from_string(
- ''
- "a,b ",
- )
-
class TestPointLxml(Lxml, TestPoint):
"""Test with lxml."""
diff --git a/tests/geometries/polygon_test.py b/tests/geometries/polygon_test.py
index 13763494..ed9ee2e1 100644
--- a/tests/geometries/polygon_test.py
+++ b/tests/geometries/polygon_test.py
@@ -17,7 +17,11 @@
"""Test the geometry classes."""
import pygeoif.geometry as geo
+import pytest
+from fastkml.enums import AltitudeMode
+from fastkml.enums import Verbosity
+from fastkml.exceptions import GeometryError
from fastkml.geometry import OuterBoundaryIs
from fastkml.geometry import Polygon
from tests.base import Lxml
@@ -39,7 +43,7 @@ def test_exterior_only(self) -> None:
assert (
"0.000000,0.000000 0.000000,1.000000 1.000000,1.000000 "
"1.000000,0.000000 0.000000,0.000000"
- ) in polygon.to_string()
+ ) in polygon.to_string(precision=6)
def test_exterior_interior(self) -> None:
"""Test exterior and interior."""
@@ -56,11 +60,83 @@ def test_exterior_interior(self) -> None:
assert (
"0.000000,0.000000 0.000000,1.000000 1.000000,1.000000 "
"1.000000,0.000000 0.000000,0.000000"
- ) in polygon.to_string()
+ ) in polygon.to_string(precision=6)
assert (
"0.100000,0.100000 0.100000,0.900000 0.900000,0.900000 "
"0.900000,0.100000 0.100000,0.100000"
- ) in polygon.to_string()
+ ) in polygon.to_string(precision=6)
+
+ def test_exterior_interior_tessellate_extrude_altitude_mode(self) -> None:
+ """
+ Test exterior and interior with tessellate, extrude and altitude mode.
+
+ This should be set on the Polygon level, not on the LinearRing level.
+ """
+ poly = geo.Polygon(
+ [(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)],
+ [[(0.1, 0.1), (0.1, 0.9), (0.9, 0.9), (0.9, 0.1), (0.1, 0.1)]],
+ )
+ polygon = Polygon(
+ ns="",
+ geometry=poly,
+ extrude=True,
+ tessellate=True,
+ altitude_mode=AltitudeMode.relative_to_ground,
+ )
+
+ xml = polygon.to_string()
+ assert xml.count("extrude>1") == 1
+ assert xml.count("tessellate>1") == 1
+ assert xml.count("altitudeMode") == 2
+ assert xml.count(">relativeToGround") == 1
+
+ def test_to_string_terse_default(self) -> None:
+ """Test the to_string method, exclude default for extrude in terse mode."""
+ poly = geo.Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
+
+ polygon = Polygon(ns="", geometry=poly, extrude=False)
+
+ assert "extrude>0" not in polygon.to_string(verbosity=Verbosity.terse)
+
+ def test_to_string_terse_non_default(self) -> None:
+ """Test the to_string method, include extrude when true in terse mode."""
+ poly = geo.Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
+
+ polygon = Polygon(ns="", geometry=poly, extrude=True)
+
+ assert "extrude>1" in polygon.to_string(verbosity=Verbosity.terse)
+
+ def test_to_string_verbose_default(self) -> None:
+ """Test the to_string method, include default for extrude in verbose mode."""
+ poly = geo.Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
+
+ polygon = Polygon(ns="", geometry=poly, extrude=False)
+
+ assert "extrude>0" in polygon.to_string(verbosity=Verbosity.verbose)
+
+ def test_to_string_verbose_non_default(self) -> None:
+ """Test the to_string method, include extrude when true in verbose mode."""
+ poly = geo.Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
+
+ polygon = Polygon(ns="", geometry=poly, extrude=True)
+
+ assert "extrude>1" in polygon.to_string(verbosity=Verbosity.verbose)
+
+ def test_to_string_verbose_none(self) -> None:
+ """Test the to_string method, include extrude when true in verbose mode."""
+ poly = geo.Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
+
+ polygon = Polygon(ns="", geometry=poly)
+
+ assert "extrude>0" in polygon.to_string(verbosity=Verbosity.verbose)
+
+ def test_geometry_error(self) -> None:
+ """Test GeometryError."""
+ poly = geo.Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
+ ob = OuterBoundaryIs(ns="")
+
+ with pytest.raises(GeometryError):
+ Polygon(geometry=poly, outer_boundary=ob)
def test_from_string_exterior_only(self) -> None:
"""Test exterior only."""
@@ -73,7 +149,7 @@ def test_from_string_exterior_only(self) -> None:
"""
- polygon2 = Polygon.class_from_string(doc)
+ polygon2 = Polygon.from_string(doc)
assert polygon2.geometry == geo.Polygon([(0, 0), (1, 0), (1, 1), (0, 0)])
@@ -88,7 +164,7 @@ def test_from_string_interiors_only(self) -> None:
"""
- assert not Polygon.class_from_string(doc)
+ assert not Polygon.from_string(doc)
def test_from_string_exterior_wo_linearring(self) -> None:
"""Test exterior when no LinearRing in outer boundary."""
@@ -99,7 +175,7 @@ def test_from_string_exterior_wo_linearring(self) -> None:
"""
- assert not Polygon.class_from_string(doc)
+ assert not Polygon.from_string(doc)
def test_from_string_interior_wo_linearring(self) -> None:
"""Test interior when no LinearRing in inner boundary."""
@@ -116,7 +192,7 @@ def test_from_string_interior_wo_linearring(self) -> None:
"""
- poly = Polygon.class_from_string(doc)
+ poly = Polygon.from_string(doc)
assert poly.geometry == geo.Polygon(
((0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)),
@@ -139,7 +215,7 @@ def test_from_string_exterior_interior(self) -> None:
"""
- polygon = Polygon.class_from_string(doc)
+ polygon = Polygon.from_string(doc)
assert polygon.geometry == geo.Polygon(
[(-1, -1), (2, -1), (2, 2), (-1, -1)],
@@ -163,7 +239,7 @@ def test_from_string_exterior_mixed_interior_relaxed(self) -> None:
"""
- polygon = Polygon.class_from_string(doc, strict=False)
+ polygon = Polygon.from_string(doc, strict=False)
assert polygon.geometry == geo.Polygon(
((-1.0, -1.0), (2.0, -1.0), (2.0, 2.0), (-1.0, -1.0)),
@@ -177,7 +253,7 @@ def test_empty_polygon(self) -> None:
""
)
- polygon = Polygon.class_from_string(doc)
+ polygon = Polygon.from_string(doc)
assert not polygon.geometry
assert polygon.outer_boundary is not None
diff --git a/tests/gx_test.py b/tests/gx_test.py
index 90d67580..8ad064d6 100644
--- a/tests/gx_test.py
+++ b/tests/gx_test.py
@@ -19,13 +19,14 @@
import pygeoif.geometry as geo
import pytest
+from dateutil.tz import tzoffset
from dateutil.tz import tzutc
-from fastkml.enums import AltitudeMode
from fastkml.gx import Angle
from fastkml.gx import MultiTrack
from fastkml.gx import Track
from fastkml.gx import TrackItem
+from fastkml.times import KmlDateTime
from tests.base import Lxml
from tests.base import StdLibrary
@@ -43,7 +44,7 @@ def test_track(self) -> None:
0.000000 0.000000
1.000000 1.000000
"""
- g = Track.class_from_string(doc, ns="")
+ g = Track.from_string(doc, ns="")
assert g.geometry.__geo_interface__ == {
"type": "LineString",
@@ -51,6 +52,13 @@ def test_track(self) -> None:
"coordinates": ((0.0, 0.0), (1.0, 1.0)),
}
+ def test_track_etree_element(self) -> None:
+ g = Track()
+
+ g.etree_element()
+
+ assert g.track_items == []
+
def test_multitrack(self) -> None:
doc = """
None:
"""
- mt = MultiTrack.class_from_string(doc, ns="")
+ mt = MultiTrack.from_string(doc)
assert mt.geometry == geo.MultiLineString(
(((0.0, 0.0), (1.0, 0.0)), ((0.0, 1.0), (1.0, 1.0))),
)
assert "when>" in mt.to_string()
- assert (
- mt.to_string()
- == MultiTrack(
- ns="",
- id="",
- target_id="",
- extrude=None,
- tessellate=None,
- altitude_mode=None,
- tracks=[
- Track(
- ns="{http://www.google.com/kml/ext/2.2}",
- id="",
- target_id="",
- extrude=None,
- tessellate=None,
- altitude_mode=None,
- track_items=[
- TrackItem(
- when=datetime.datetime(
+ assert mt == MultiTrack(
+ tracks=[
+ Track(
+ track_items=[
+ TrackItem(
+ when=KmlDateTime(
+ dt=datetime.datetime(
2020,
1,
1,
0,
0,
- tzinfo=tzutc(),
+ tzinfo=tzoffset(None, 0),
),
- coord=geo.Point(0.0, 0.0),
- angle=None,
),
- TrackItem(
- when=datetime.datetime(
+ coord=geo.Point(0.0, 0.0),
+ angle=Angle(heading=0.0, tilt=0.0, roll=0.0),
+ ),
+ TrackItem(
+ when=KmlDateTime(
+ dt=datetime.datetime(
2020,
1,
1,
0,
10,
- tzinfo=tzutc(),
+ tzinfo=tzoffset(None, 0),
),
- coord=geo.Point(1.0, 0.0),
- angle=None,
),
- ],
- ),
- Track(
- ns="{http://www.google.com/kml/ext/2.2}",
- id="",
- target_id="",
- extrude=None,
- tessellate=None,
- altitude_mode=None,
- track_items=[
- TrackItem(
- when=datetime.datetime(
+ coord=geo.Point(1.0, 0.0),
+ angle=Angle(heading=0.0, tilt=0.0, roll=0.0),
+ ),
+ ],
+ ),
+ Track(
+ track_items=[
+ TrackItem(
+ when=KmlDateTime(
+ dt=datetime.datetime(
2020,
1,
1,
0,
10,
- tzinfo=tzutc(),
+ tzinfo=tzoffset(None, 0),
),
- coord=geo.Point(0.0, 1.0),
- angle=None,
),
- TrackItem(
- when=datetime.datetime(
+ coord=geo.Point(0.0, 1.0),
+ angle=Angle(heading=0.0, tilt=0.0, roll=0.0),
+ ),
+ TrackItem(
+ when=KmlDateTime(
+ dt=datetime.datetime(
2020,
1,
1,
0,
20,
- tzinfo=tzutc(),
+ tzinfo=tzoffset(None, 0),
),
- coord=geo.Point(1.0, 1.0),
- angle=None,
),
- ],
- ),
- ],
- interpolate=None,
- ).to_string()
+ coord=geo.Point(1.0, 1.0),
+ angle=Angle(heading=0.0, tilt=0.0, roll=0.0),
+ ),
+ ],
+ ),
+ ],
)
class TestTrack(StdLibrary):
"""Test gx.Track."""
- def test_track_from_linestring(self) -> None:
- ls = geo.LineString(((1, 2), (2, 0)))
-
- track = Track(
- ns="",
- id="track1",
- target_id="track2",
- altitude_mode=AltitudeMode.absolute,
- extrude=True,
- tessellate=True,
- geometry=ls,
- )
-
- assert "1" in track.to_string()
- assert "tessellate>1" in track.to_string()
- assert "altitudeMode>absolute" in track.to_string()
- assert "coord>" in track.to_string()
- assert "angles" in track.to_string()
- assert "when" in track.to_string()
- assert "angles>" not in track.to_string()
- assert "when>" not in track.to_string()
-
def test_track_from_track_items(self) -> None:
- time1 = datetime.datetime(2023, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc)
+ time1 = KmlDateTime(
+ datetime.datetime(2023, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc),
+ )
angle = Angle()
track_items = [TrackItem(when=time1, coord=geo.Point(1, 2), angle=angle)]
@@ -205,34 +176,76 @@ def test_track_from_track_items(self) -> None:
assert "angles>" in track.to_string()
assert ">0.0 0.0 0.0" in track.to_string()
- def test_track_from_track_items_and_geometry(self) -> None:
- ls = geo.LineString(((1, 2), (2, 0)))
- time1 = datetime.datetime(2023, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc)
+ def test_track_from_whens_and_coords(self) -> None:
+ whens = [
+ KmlDateTime(
+ datetime.datetime(2023, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc),
+ ),
+ ]
+ coords = [(1, 2)]
+
+ track = Track(
+ whens=whens,
+ coords=coords,
+ )
+
+ assert "when>" in track.to_string()
+ assert ">2023-01-01T00:00:00+00:00" in track.to_string()
+ assert "coord>" in track.to_string()
+ assert ">1 2" in track.to_string()
+ assert track.coords == ((1, 2),)
+
+ def test_track_from_whens_and_coords_and_track_items(self) -> None:
+ whens = [
+ KmlDateTime(
+ datetime.datetime(2023, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc),
+ ),
+ ]
+ coords = [(1, 2)]
+ time1 = KmlDateTime(
+ datetime.datetime(2023, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc),
+ )
angle = Angle()
track_items = [TrackItem(when=time1, coord=geo.Point(1, 2), angle=angle)]
- with pytest.raises(ValueError):
+ with pytest.raises(
+ ValueError,
+ match="^Cannot specify both geometry and track_items$",
+ ):
Track(
+ whens=whens,
+ coords=coords,
track_items=track_items,
- geometry=ls,
)
- def test_track_from_track_items_no_coord(self) -> None:
- time1 = datetime.datetime(2023, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc)
- angle = Angle()
- track_items = [TrackItem(when=time1, coord=None, angle=angle)]
-
+ def test_track_precision(self) -> None:
track = Track(
- ns="",
- track_items=track_items,
+ id="x",
+ target_id="y",
+ altitude_mode=None,
+ track_items=[
+ TrackItem(
+ when=KmlDateTime(
+ dt=datetime.datetime(2010, 5, 28, 2, 2, 9, tzinfo=tzutc()),
+ ),
+ coord=geo.Point(-122.207881, 37.371915, 156.0),
+ angle=Angle(heading=45.54676, tilt=66.2342, roll=77.0),
+ ),
+ TrackItem(
+ when=KmlDateTime(
+ dt=datetime.datetime(2010, 5, 28, 2, 2, 35, tzinfo=tzutc()),
+ ),
+ coord=geo.Point(-122.205712, 37.373288, 152.0),
+ angle=Angle(heading=1.0, tilt=2.0, roll=3.0),
+ ),
+ ],
)
- assert "when>" in track.to_string()
- assert ">2023-01-01T00:00:00+00:00" in track.to_string()
- assert "coord>" not in track.to_string()
- assert "coord" in track.to_string()
- assert "angles>" in track.to_string()
- assert ">0.0 0.0 0.0" in track.to_string()
+ xml = track.to_string(precision=2)
+ assert "angles>45.55 66.23 77.00" in xml
+ assert "angles>1.00 2.00 3.00" in xml
+ assert "coord>-122.21 37.37 156.00" in xml
+ assert "coord>-122.21 37.37 152.00" in xml
def test_track_from_str(self) -> None:
doc = """
@@ -245,7 +258,7 @@ def test_track_from_str(self) -> None:
2010-05-28T02:02:54Z
2010-05-28T02:02:55Z
2010-05-28T02:02:56Z
-
+ 2010-05-28T02:02:57Z
45.54676 66.2342 77.0
1 2 3
@@ -258,64 +271,83 @@ def test_track_from_str(self) -> None:
-122.205712 37.373288 152.000000
-122.204678 37.373939 147.000000
-122.203572 37.374630 142.199997
-
+ -112.203451 37.374690 141.800000
-122.203451 37.374706 141.800003
-122.203329 37.374780 141.199997
-122.203207 37.374857 140.199997
"""
expected_track = Track(
- ns="",
+ ns="{http://www.google.com/kml/ext/2.2}",
+ name_spaces={
+ "kml": "{http://www.opengis.net/kml/2.2}",
+ "atom": "{http://www.w3.org/2005/Atom}",
+ "gx": "{http://www.google.com/kml/ext/2.2}",
+ },
id="",
target_id="",
- extrude=None,
- tessellate=None,
altitude_mode=None,
track_items=[
TrackItem(
- when=datetime.datetime(2010, 5, 28, 2, 2, 9, tzinfo=tzutc()),
+ when=KmlDateTime(
+ dt=datetime.datetime(2010, 5, 28, 2, 2, 9, tzinfo=tzutc()),
+ ),
coord=geo.Point(-122.207881, 37.371915, 156.0),
angle=Angle(heading=45.54676, tilt=66.2342, roll=77.0),
),
TrackItem(
- when=datetime.datetime(2010, 5, 28, 2, 2, 35, tzinfo=tzutc()),
+ when=KmlDateTime(
+ dt=datetime.datetime(2010, 5, 28, 2, 2, 35, tzinfo=tzutc()),
+ ),
coord=geo.Point(-122.205712, 37.373288, 152.0),
- angle=None,
+ angle=Angle(heading=1.0, tilt=2.0, roll=3.0),
),
TrackItem(
- when=datetime.datetime(2010, 5, 28, 2, 2, 44, tzinfo=tzutc()),
+ when=KmlDateTime(
+ dt=datetime.datetime(2010, 5, 28, 2, 2, 44, tzinfo=tzutc()),
+ ),
coord=geo.Point(-122.204678, 37.373939, 147.0),
angle=Angle(heading=1.0, tilt=2.0, roll=3.0),
),
TrackItem(
- when=datetime.datetime(2010, 5, 28, 2, 2, 53, tzinfo=tzutc()),
+ when=KmlDateTime(
+ dt=datetime.datetime(2010, 5, 28, 2, 2, 53, tzinfo=tzutc()),
+ ),
coord=geo.Point(-122.203572, 37.37463, 142.199997),
angle=Angle(heading=1.0, tilt=2.0, roll=3.0),
),
TrackItem(
- when=datetime.datetime(2010, 5, 28, 2, 2, 54, tzinfo=tzutc()),
- coord=None,
+ when=KmlDateTime(
+ dt=datetime.datetime(2010, 5, 28, 2, 2, 54, tzinfo=tzutc()),
+ ),
+ coord=geo.Point(-112.203451, 37.37469, 141.8),
angle=Angle(heading=1.0, tilt=2.0, roll=3.0),
),
TrackItem(
- when=datetime.datetime(2010, 5, 28, 2, 2, 55, tzinfo=tzutc()),
+ when=KmlDateTime(
+ dt=datetime.datetime(2010, 5, 28, 2, 2, 55, tzinfo=tzutc()),
+ ),
coord=geo.Point(-122.203451, 37.374706, 141.800003),
angle=Angle(heading=1.0, tilt=2.0, roll=3.0),
),
TrackItem(
- when=datetime.datetime(2010, 5, 28, 2, 2, 56, tzinfo=tzutc()),
+ when=KmlDateTime(
+ dt=datetime.datetime(2010, 5, 28, 2, 2, 56, tzinfo=tzutc()),
+ ),
coord=geo.Point(-122.203329, 37.37478, 141.199997),
angle=Angle(heading=1.0, tilt=2.0, roll=3.0),
),
TrackItem(
- when=None,
+ when=KmlDateTime(
+ dt=datetime.datetime(2010, 5, 28, 2, 2, 57, tzinfo=tzutc()),
+ ),
coord=geo.Point(-122.203207, 37.374857, 140.199997),
- angle=Angle(heading=1.0, tilt=2.0, roll=3.0),
+ angle=Angle(heading=0.0, tilt=0.0, roll=0.0),
),
],
)
- track = Track.class_from_string(doc, ns="")
+ track = Track.from_string(doc)
assert track.geometry == geo.LineString(
(
@@ -323,61 +355,46 @@ def test_track_from_str(self) -> None:
(-122.205712, 37.373288, 152.0),
(-122.204678, 37.373939, 147.0),
(-122.203572, 37.37463, 142.199997),
+ (-112.203451, 37.37469, 141.8),
(-122.203451, 37.374706, 141.800003),
(-122.203329, 37.37478, 141.199997),
(-122.203207, 37.374857, 140.199997),
),
)
+
assert track.to_string() == expected_track.to_string()
+ def test_track_from_str_invalid_when(self) -> None:
+ doc = """
+
+ 2010-02-32T02:02:09Z
+ 45.54676 66.2342 77.0
+ -122.207881 37.371915 156.000000
+
+ """
+
+ track = Track.from_string(doc, strict=False)
-class TestMultiTrack(StdLibrary):
- def test_from_multilinestring(self) -> None:
- lines = geo.MultiLineString(
- (((0, 0), (1, 1), (1, 2), (2, 2)), ((0.0, 0.0), (1.0, 2.0))),
- )
+ assert track.track_items == []
- mt = MultiTrack(geometry=lines, ns="")
-
- assert (
- mt.to_string()
- == MultiTrack(
- ns="",
- id=None,
- target_id=None,
- extrude=None,
- tessellate=None,
- altitude_mode=None,
- tracks=[
- Track(
- ns="",
- id=None,
- target_id=None,
- extrude=None,
- tessellate=None,
- altitude_mode=None,
- track_items=[
- TrackItem(when=None, coord=geo.Point(0, 0), angle=None),
- TrackItem(when=None, coord=geo.Point(1, 1), angle=None),
- TrackItem(when=None, coord=geo.Point(1, 2), angle=None),
- TrackItem(when=None, coord=geo.Point(2, 2), angle=None),
- ],
- ),
- Track(
- ns="",
- id=None,
- target_id=None,
- extrude=None,
- tessellate=None,
- altitude_mode=None,
- track_items=[
- TrackItem(when=None, coord=geo.Point(0.0, 0.0), angle=None),
- TrackItem(when=None, coord=geo.Point(1.0, 2.0), angle=None),
- ],
- ),
- ],
- ).to_string()
- )
+ def test_track_from_str_invalid_coord(self) -> None:
+ doc = """
+
+ 2010-02-14T02:02:09Z
+ 45.54676 66.2342 77.0
+ XYZ 37.371915 156.000000
+
+ """
+
+ track = Track.from_string(doc, strict=False)
+
+ assert track.track_items == []
+
+
+class TestMultiTrack(StdLibrary):
+ """Test gx.MultiTrack."""
def test_multitrack(self) -> None:
track = MultiTrack(
@@ -387,37 +404,97 @@ def test_multitrack(self) -> None:
Track(
ns="",
track_items=[
- TrackItem(when=None, coord=geo.Point(0, 0), angle=None),
- TrackItem(when=None, coord=geo.Point(1, 1), angle=None),
- TrackItem(when=None, coord=geo.Point(1, 2), angle=None),
- TrackItem(when=None, coord=geo.Point(2, 2), angle=None),
+ TrackItem(
+ when=KmlDateTime(
+ datetime.datetime(
+ 2010,
+ 5,
+ 28,
+ 2,
+ 2,
+ 55,
+ tzinfo=tzutc(),
+ ),
+ ),
+ coord=geo.Point(0, 0),
+ angle=None,
+ ),
+ TrackItem(
+ when=KmlDateTime(
+ datetime.datetime(
+ 2010,
+ 5,
+ 28,
+ 2,
+ 2,
+ 56,
+ tzinfo=tzutc(),
+ ),
+ ),
+ coord=geo.Point(1, 1),
+ angle=None,
+ ),
+ TrackItem(
+ when=KmlDateTime(
+ datetime.datetime(
+ 2010,
+ 5,
+ 28,
+ 2,
+ 2,
+ 57,
+ tzinfo=tzutc(),
+ ),
+ ),
+ coord=geo.Point(1, 2),
+ angle=None,
+ ),
+ TrackItem(
+ when=KmlDateTime(
+ datetime.datetime(
+ 2010,
+ 5,
+ 28,
+ 2,
+ 2,
+ 58,
+ tzinfo=tzutc(),
+ ),
+ ),
+ coord=geo.Point(2, 2),
+ angle=None,
+ ),
],
),
Track(
ns="",
track_items=[
TrackItem(
- when=datetime.datetime(
- 2010,
- 5,
- 28,
- 2,
- 2,
- 55,
- tzinfo=tzutc(),
+ when=KmlDateTime(
+ datetime.datetime(
+ 2010,
+ 5,
+ 28,
+ 2,
+ 2,
+ 55,
+ tzinfo=tzutc(),
+ ),
),
coord=geo.Point(-122.203451, 37.374706, 141.800003),
angle=Angle(heading=1.0, tilt=2.0, roll=3.0),
),
TrackItem(
- when=datetime.datetime(
- 2010,
- 5,
- 28,
- 2,
- 2,
- 56,
- tzinfo=tzutc(),
+ when=KmlDateTime(
+ datetime.datetime(
+ 2010,
+ 5,
+ 28,
+ 2,
+ 2,
+ 56,
+ tzinfo=tzutc(),
+ ),
),
coord=geo.Point(-122.203329, 37.37478, 141.199997),
angle=Angle(heading=1.0, tilt=2.0, roll=3.0),
@@ -443,29 +520,6 @@ def test_multitrack(self) -> None:
assert "angles>" in track.to_string()
assert "when>" in track.to_string()
- def test_from_multilinestring_and_tracks(self) -> None:
- lines = geo.MultiLineString(
- (((0, 0), (1, 1), (1, 2), (2, 2)), ((0.0, 0.0), (1.0, 2.0))),
- )
- track_items = [
- TrackItem(
- when=datetime.datetime(
- 2010,
- 5,
- 28,
- 2,
- 2,
- 55,
- tzinfo=tzutc(),
- ),
- coord=geo.Point(-122.203451, 37.374706, 141.800003),
- angle=Angle(heading=1.0, tilt=2.0, roll=3.0),
- ),
- ]
-
- with pytest.raises(ValueError):
- MultiTrack(geometry=lines, tracks=[Track(track_items=track_items)])
-
class TestLxml(Lxml, TestStdLibrary):
"""Test with lxml."""
diff --git a/tests/helper_test.py b/tests/helper_test.py
new file mode 100644
index 00000000..cd1ae8b7
--- /dev/null
+++ b/tests/helper_test.py
@@ -0,0 +1,147 @@
+# Copyright (C) 2024 Rishit Chaudhary, Christian Ledermann
+#
+# This library is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This library is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+"""Test the helper functions edge cases."""
+from enum import Enum
+from typing import Callable
+from unittest.mock import Mock
+from unittest.mock import patch
+
+from fastkml.helpers import attribute_enum_kwarg
+from fastkml.helpers import attribute_float_kwarg
+from fastkml.helpers import subelement_bool_kwarg
+from fastkml.helpers import subelement_enum_kwarg
+from fastkml.helpers import subelement_float_kwarg
+from fastkml.helpers import subelement_int_kwarg
+from tests.base import StdLibrary
+
+
+class Node:
+ text: str
+
+
+class Color(Enum):
+ RED = 1
+
+
+class TestStdLibrary(StdLibrary):
+ """Test with the standard library."""
+
+ def test_subelement_int_kwarg(self) -> None:
+ node = Node()
+ node.text = ""
+ element = Mock()
+ element.find.return_value = node
+ res = subelement_int_kwarg(
+ element=element,
+ ns="ns",
+ name_spaces={"name": "uri"},
+ node_name="node",
+ kwarg="kwarg",
+ classes=(int,),
+ strict=False,
+ )
+ assert res == {}
+
+ def test_subelement_float_kwarg(self) -> None:
+ node = Node()
+ node.text = ""
+ element = Mock()
+ element.find.return_value = node
+ res = subelement_float_kwarg(
+ element=element,
+ ns="ns",
+ name_spaces={"name": "uri"},
+ node_name="node",
+ kwarg="kwarg",
+ classes=(float,),
+ strict=False,
+ )
+ assert res == {}
+
+ @patch("fastkml.helpers.handle_error")
+ def test_attribute_float_kwarg(
+ self,
+ mock_handle_error: Callable[..., None],
+ ) -> None:
+ element = Mock()
+ element.get.return_value = "abcd"
+
+ res = attribute_float_kwarg(
+ element=element,
+ ns="ns",
+ name_spaces={"name": "uri"},
+ node_name="node",
+ kwarg="a",
+ classes=(float,),
+ strict=True,
+ )
+
+ assert res == {}
+ mock_handle_error.assert_called_once() # type: ignore[attr-defined]
+
+ def test_subelement_enum_kwarg(self) -> None:
+ node = Node()
+ node.text = ""
+ element = Mock()
+ element.find.return_value = node
+
+ res = subelement_enum_kwarg(
+ element=element,
+ ns="ns",
+ name_spaces={"name": "uri"},
+ node_name="node",
+ kwarg="a",
+ classes=(Color,),
+ strict=True,
+ )
+
+ assert res == {}
+ element.find.assert_called_once_with("nsnode")
+
+ def test_attribute_enum_kwarg(self) -> None:
+ element = Mock()
+ element.get.return_value = None
+
+ res = attribute_enum_kwarg(
+ element=element,
+ ns="ns",
+ name_spaces={"name": "uri"},
+ node_name="node",
+ kwarg="a",
+ classes=(Color,),
+ strict=True,
+ )
+
+ assert res == {}
+ element.get.assert_called_once_with("nsnode")
+
+ def test_subelement_bool_kwarg(self) -> None:
+ node = Node()
+ node.text = ""
+ element = Mock()
+ element.find.return_value = node
+ res = subelement_bool_kwarg(
+ element=element,
+ ns="ns",
+ name_spaces={"name": "uri"},
+ node_name="node",
+ kwarg="a",
+ classes=(bool,),
+ strict=True,
+ )
+
+ assert res == {}
+ element.find.assert_called_once_with("nsnode")
diff --git a/tests/hypothesis/__init__.py b/tests/hypothesis/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/hypothesis/atom_test.py b/tests/hypothesis/atom_test.py
new file mode 100644
index 00000000..4cf0cbe8
--- /dev/null
+++ b/tests/hypothesis/atom_test.py
@@ -0,0 +1,91 @@
+# Copyright (C) 2024 Christian Ledermann
+#
+# This library is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This library is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+"""
+Property-based tests for Link and Author classes using Hypothesis.
+
+This module implements fuzz testing to verify the serialization/deserialization
+roundtrip and string representation of Link and Author classes under various
+input conditions.
+"""
+
+import typing
+
+from hypothesis import given
+from hypothesis import strategies as st
+from hypothesis.provisional import urls
+
+import fastkml
+import fastkml.enums
+from tests.base import Lxml
+from tests.hypothesis.common import assert_repr_roundtrip
+from tests.hypothesis.common import assert_str_roundtrip
+from tests.hypothesis.common import assert_str_roundtrip_terse
+from tests.hypothesis.common import assert_str_roundtrip_verbose
+from tests.hypothesis.strategies import href_langs
+from tests.hypothesis.strategies import media_types
+from tests.hypothesis.strategies import xml_text
+
+
+class TestLxml(Lxml):
+
+ @given(
+ href=urls(),
+ rel=st.one_of(st.none(), xml_text()),
+ type=st.one_of(st.none(), media_types()),
+ hreflang=st.one_of(st.none(), href_langs()),
+ title=st.one_of(st.none(), xml_text()),
+ length=st.one_of(st.none(), st.integers()),
+ )
+ def test_fuzz_link(
+ self,
+ href: typing.Optional[str],
+ rel: typing.Optional[str],
+ type: typing.Optional[str],
+ hreflang: typing.Optional[str],
+ title: typing.Optional[str],
+ length: typing.Optional[int],
+ ) -> None:
+ link = fastkml.atom.Link(
+ href=href,
+ rel=rel,
+ type=type,
+ hreflang=hreflang,
+ title=title,
+ length=length,
+ )
+
+ assert_repr_roundtrip(link)
+ assert_str_roundtrip(link)
+ assert_str_roundtrip_terse(link)
+ assert_str_roundtrip_verbose(link)
+
+ @given(
+ name=st.one_of(st.none(), xml_text()),
+ uri=st.one_of(st.none(), urls()),
+ email=st.one_of(st.none(), st.emails()),
+ )
+ def test_fuzz_author(
+ self,
+ name: typing.Optional[str],
+ uri: typing.Optional[str],
+ email: typing.Optional[str],
+ ) -> None:
+ author = fastkml.atom.Author(name=name, uri=uri, email=email)
+
+ assert_repr_roundtrip(author)
+ assert_str_roundtrip(author)
+ assert_str_roundtrip_terse(author)
+ assert_str_roundtrip_verbose(author)
diff --git a/tests/hypothesis/common.py b/tests/hypothesis/common.py
new file mode 100644
index 00000000..53dee19e
--- /dev/null
+++ b/tests/hypothesis/common.py
@@ -0,0 +1,107 @@
+# Copyright (C) 2024 Christian Ledermann
+#
+# This library is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This library is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+"""Common functionality for property based tests."""
+import datetime
+import logging
+
+from dateutil.tz import tzfile
+from dateutil.tz import tzutc
+from pygeoif import GeometryCollection
+from pygeoif import MultiLineString
+from pygeoif import MultiPoint
+from pygeoif import MultiPolygon
+from pygeoif.geometry import LinearRing
+from pygeoif.geometry import LineString
+from pygeoif.geometry import Point
+from pygeoif.geometry import Polygon
+
+import fastkml
+from fastkml.base import _XMLObject
+from fastkml.enums import AltitudeMode
+from fastkml.enums import DateTimeResolution
+from fastkml.enums import RefreshMode
+from fastkml.enums import Verbosity
+from fastkml.enums import ViewRefreshMode
+from fastkml.gx import Angle
+from fastkml.gx import TrackItem
+from fastkml.validator import validate
+
+logger = logging.getLogger(__name__)
+
+eval_locals = {
+ "Point": Point,
+ "Polygon": Polygon,
+ "LineString": LineString,
+ "LinearRing": LinearRing,
+ "MultiPoint": MultiPoint,
+ "MultiLineString": MultiLineString,
+ "MultiPolygon": MultiPolygon,
+ "GeometryCollection": GeometryCollection,
+ "AltitudeMode": AltitudeMode,
+ "fastkml": fastkml,
+ "ViewRefreshMode": ViewRefreshMode,
+ "RefreshMode": RefreshMode,
+ "TrackItem": TrackItem,
+ "Angle": Angle,
+ "datetime": datetime,
+ "DateTimeResolution": DateTimeResolution,
+ "tzutc": tzutc,
+ "tzfile": tzfile,
+}
+
+
+def assert_repr_roundtrip(obj: _XMLObject) -> None:
+ """Test that repr(obj) can be eval'd back to obj."""
+ try:
+ assert obj == eval(repr(obj), {}, eval_locals) # noqa: S307
+ except FileNotFoundError:
+ # The timezone file may not be available on all systems.
+ logger.exception("Failed to eval repr(obj).")
+
+
+def assert_str_roundtrip(obj: _XMLObject) -> None:
+ """
+ Test that an XML object can be serialized and deserialized without changes.
+
+ Uses default verbosity settings and validates the resulting XML structure.
+ """
+ new_object = type(obj).from_string(obj.to_string())
+
+ assert obj.to_string() == new_object.to_string()
+ assert obj == new_object
+ assert validate(element=new_object.etree_element())
+
+
+def assert_str_roundtrip_terse(obj: _XMLObject) -> None:
+ new_object = type(obj).from_string(
+ obj.to_string(verbosity=Verbosity.terse),
+ )
+
+ assert obj.to_string(verbosity=Verbosity.verbose) == new_object.to_string(
+ verbosity=Verbosity.verbose,
+ )
+ assert validate(element=new_object.etree_element())
+
+
+def assert_str_roundtrip_verbose(obj: _XMLObject) -> None:
+ new_object = type(obj).from_string(
+ obj.to_string(verbosity=Verbosity.verbose),
+ )
+
+ assert obj.to_string(verbosity=Verbosity.terse) == new_object.to_string(
+ verbosity=Verbosity.terse,
+ )
+ assert validate(element=new_object.etree_element())
diff --git a/tests/hypothesis/geometry_test.py b/tests/hypothesis/geometry_test.py
new file mode 100644
index 00000000..0e85323d
--- /dev/null
+++ b/tests/hypothesis/geometry_test.py
@@ -0,0 +1,484 @@
+# Copyright (C) 2024 Christian Ledermann
+#
+# This library is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This library is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""Property based tests of the Geometry classes."""
+import typing
+from functools import partial
+
+from hypothesis import given
+from hypothesis import settings
+from hypothesis import strategies as st
+from pygeoif.geometry import LinearRing
+from pygeoif.geometry import LineString
+from pygeoif.geometry import Point
+from pygeoif.geometry import Polygon
+from pygeoif.hypothesis.strategies import epsg4326
+from pygeoif.hypothesis.strategies import line_coords
+from pygeoif.hypothesis.strategies import line_strings
+from pygeoif.hypothesis.strategies import points
+from pygeoif.hypothesis.strategies import polygons
+
+import fastkml.geometry
+from fastkml.enums import AltitudeMode
+from fastkml.enums import Verbosity
+from fastkml.validator import validate
+from tests.base import Lxml
+from tests.hypothesis.common import assert_repr_roundtrip
+from tests.hypothesis.common import assert_str_roundtrip
+from tests.hypothesis.strategies import nc_name
+
+eval_locals = {
+ "Point": Point,
+ "Polygon": Polygon,
+ "LineString": LineString,
+ "LinearRing": LinearRing,
+ "AltitudeMode": AltitudeMode,
+ "fastkml": fastkml,
+}
+
+kml_geometry = typing.Union[
+ fastkml.geometry.Point,
+ fastkml.geometry.LineString,
+ fastkml.geometry.Polygon,
+]
+
+coordinates = partial(
+ given,
+ coords=st.one_of(st.none(), line_coords(srs=epsg4326, min_points=1)),
+)
+
+common_geometry = partial(
+ given,
+ id=st.one_of(st.none(), nc_name()),
+ target_id=st.one_of(st.none(), nc_name()),
+ extrude=st.one_of(st.none(), st.booleans()),
+ tessellate=st.one_of(st.none(), st.booleans()),
+ altitude_mode=st.one_of(
+ st.none(),
+ st.sampled_from(
+ (
+ AltitudeMode.absolute,
+ AltitudeMode.clamp_to_ground,
+ AltitudeMode.relative_to_ground,
+ ),
+ ),
+ ),
+)
+
+
+def _test_repr_roundtrip(geometry: kml_geometry) -> None:
+ assert_repr_roundtrip(geometry)
+
+
+def _test_geometry_str_roundtrip(geometry: kml_geometry) -> None:
+ assert_str_roundtrip(geometry)
+
+
+def _test_geometry_str_roundtrip_terse(geometry: kml_geometry) -> None:
+ new_g = type(geometry).from_string(
+ geometry.to_string(verbosity=Verbosity.terse),
+ )
+
+ assert validate(element=new_g.etree_element())
+ assert geometry.to_string(verbosity=Verbosity.verbose) == new_g.to_string(
+ verbosity=Verbosity.verbose,
+ )
+ assert geometry.geometry == new_g.geometry
+ if geometry.altitude_mode == AltitudeMode.clamp_to_ground:
+ assert new_g.altitude_mode is None
+ else:
+ assert new_g.altitude_mode == geometry.altitude_mode
+ if geometry.extrude:
+ assert new_g.extrude is True
+ else:
+ assert new_g.extrude is None
+ if hasattr(geometry, "tessellate"):
+ assert not isinstance(geometry, fastkml.geometry.Point)
+ if geometry.tessellate:
+ assert new_g.tessellate is True
+ else:
+ assert new_g.tessellate is None
+
+
+def _test_geometry_str_roundtrip_verbose(geometry: kml_geometry) -> None:
+ new_g = type(geometry).from_string(
+ geometry.to_string(verbosity=Verbosity.verbose),
+ )
+
+ assert validate(element=new_g.etree_element())
+ assert geometry.to_string(verbosity=Verbosity.terse) == new_g.to_string(
+ verbosity=Verbosity.terse,
+ )
+ assert geometry.geometry == new_g.geometry
+ assert new_g.altitude_mode is not None
+ if geometry.altitude_mode is None:
+ assert new_g.altitude_mode == AltitudeMode.clamp_to_ground
+ if geometry.extrude is None:
+ assert new_g.extrude is False
+ else:
+ assert new_g.extrude == geometry.extrude
+ if hasattr(geometry, "tessellate"):
+ assert not isinstance(geometry, fastkml.geometry.Point)
+ if geometry.tessellate is None:
+ assert new_g.tessellate is False
+ else:
+ assert new_g.tessellate == geometry.tessellate
+
+
+class TestLxml(Lxml):
+
+ @coordinates()
+ @settings(deadline=None)
+ def test_coordinates_str_roundtrip(
+ self,
+ coords: typing.Union[
+ typing.Sequence[typing.Tuple[float, float]],
+ typing.Sequence[typing.Tuple[float, float, float]],
+ None,
+ ],
+ ) -> None:
+ coordinate = fastkml.geometry.Coordinates(coords=coords)
+
+ new_c = fastkml.geometry.Coordinates.from_string(
+ coordinate.to_string(precision=20),
+ )
+
+ assert coordinate.to_string(precision=10) == new_c.to_string(precision=10)
+ assert validate(element=new_c.etree_element())
+
+ @coordinates()
+ def test_coordinates_repr_roundtrip(
+ self,
+ coords: typing.Union[
+ typing.Sequence[typing.Tuple[float, float]],
+ typing.Sequence[typing.Tuple[float, float, float]],
+ None,
+ ],
+ ) -> None:
+ coordinate = fastkml.geometry.Coordinates(coords=coords)
+
+ new_c = eval(repr(coordinate), {}, eval_locals) # noqa: S307
+
+ assert coordinate == new_c
+ assert validate(element=new_c.etree_element())
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ points(srs=epsg4326),
+ ),
+ )
+ def test_point_repr_roundtrip(
+ self,
+ id: typing.Optional[str],
+ target_id: typing.Optional[str],
+ extrude: typing.Optional[bool],
+ altitude_mode: typing.Optional[AltitudeMode],
+ tessellate: typing.Optional[bool], # noqa: ARG002
+ geometry: typing.Optional[Point],
+ ) -> None:
+ point = fastkml.geometry.Point(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ _test_repr_roundtrip(point)
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ points(srs=epsg4326),
+ ),
+ )
+ def test_point_str_roundtrip(
+ self,
+ id: typing.Optional[str],
+ target_id: typing.Optional[str],
+ extrude: typing.Optional[bool],
+ tessellate: typing.Optional[bool], # noqa: ARG002
+ altitude_mode: typing.Optional[AltitudeMode],
+ geometry: typing.Optional[Point],
+ ) -> None:
+ point = fastkml.geometry.Point(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ _test_geometry_str_roundtrip(point)
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ points(srs=epsg4326),
+ ),
+ )
+ def test_point_str_roundtrip_terse(
+ self,
+ id: typing.Optional[str],
+ target_id: typing.Optional[str],
+ extrude: typing.Optional[bool],
+ tessellate: typing.Optional[bool], # noqa: ARG002
+ altitude_mode: typing.Optional[AltitudeMode],
+ geometry: typing.Optional[Point],
+ ) -> None:
+ point = fastkml.geometry.Point(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ _test_geometry_str_roundtrip_terse(point)
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ points(srs=epsg4326),
+ ),
+ )
+ def test_point_str_roundtrip_verbose(
+ self,
+ id: typing.Optional[str],
+ target_id: typing.Optional[str],
+ extrude: typing.Optional[bool],
+ tessellate: typing.Optional[bool], # noqa: ARG002
+ altitude_mode: typing.Optional[AltitudeMode],
+ geometry: typing.Optional[Point],
+ ) -> None:
+ point = fastkml.geometry.Point(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ _test_geometry_str_roundtrip_verbose(point)
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ line_strings(srs=epsg4326),
+ ),
+ )
+ def test_linestring_repr_roundtrip(
+ self,
+ id: typing.Optional[str],
+ target_id: typing.Optional[str],
+ extrude: typing.Optional[bool],
+ tessellate: typing.Optional[bool],
+ altitude_mode: typing.Optional[AltitudeMode],
+ geometry: typing.Optional[LineString],
+ ) -> None:
+ line = fastkml.geometry.LineString(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ _test_repr_roundtrip(line)
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ line_strings(srs=epsg4326),
+ ),
+ )
+ def test_linestring_str_roundtrip(
+ self,
+ id: typing.Optional[str],
+ target_id: typing.Optional[str],
+ extrude: typing.Optional[bool],
+ tessellate: typing.Optional[bool],
+ altitude_mode: typing.Optional[AltitudeMode],
+ geometry: typing.Optional[LineString],
+ ) -> None:
+ line = fastkml.geometry.LineString(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ _test_geometry_str_roundtrip(line)
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ line_strings(srs=epsg4326),
+ ),
+ )
+ def test_linestring_str_roundtrip_terse(
+ self,
+ id: typing.Optional[str],
+ target_id: typing.Optional[str],
+ extrude: typing.Optional[bool],
+ tessellate: typing.Optional[bool],
+ altitude_mode: typing.Optional[AltitudeMode],
+ geometry: typing.Optional[LineString],
+ ) -> None:
+ line = fastkml.geometry.LineString(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ _test_geometry_str_roundtrip_terse(line)
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ line_strings(srs=epsg4326),
+ ),
+ )
+ def test_linestring_str_roundtrip_verbose(
+ self,
+ id: typing.Optional[str],
+ target_id: typing.Optional[str],
+ extrude: typing.Optional[bool],
+ tessellate: typing.Optional[bool],
+ altitude_mode: typing.Optional[AltitudeMode],
+ geometry: typing.Optional[LineString],
+ ) -> None:
+ line = fastkml.geometry.LineString(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ _test_geometry_str_roundtrip_verbose(line)
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ polygons(srs=epsg4326),
+ ),
+ )
+ def test_polygon_repr_roundtrip(
+ self,
+ id: typing.Optional[str],
+ target_id: typing.Optional[str],
+ extrude: typing.Optional[bool],
+ tessellate: typing.Optional[bool],
+ altitude_mode: typing.Optional[AltitudeMode],
+ geometry: typing.Optional[Polygon],
+ ) -> None:
+ polygon = fastkml.geometry.Polygon(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ _test_repr_roundtrip(polygon)
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ polygons(srs=epsg4326),
+ ),
+ )
+ def test_polygon_str_roundtrip(
+ self,
+ id: typing.Optional[str],
+ target_id: typing.Optional[str],
+ extrude: typing.Optional[bool],
+ tessellate: typing.Optional[bool],
+ altitude_mode: typing.Optional[AltitudeMode],
+ geometry: typing.Optional[Polygon],
+ ) -> None:
+ polygon = fastkml.geometry.Polygon(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ _test_geometry_str_roundtrip(polygon)
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ polygons(srs=epsg4326),
+ ),
+ )
+ def test_polygon_str_roundtrip_terse(
+ self,
+ id: typing.Optional[str],
+ target_id: typing.Optional[str],
+ extrude: typing.Optional[bool],
+ tessellate: typing.Optional[bool],
+ altitude_mode: typing.Optional[AltitudeMode],
+ geometry: typing.Optional[Polygon],
+ ) -> None:
+ polygon = fastkml.geometry.Polygon(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ _test_geometry_str_roundtrip_terse(polygon)
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ polygons(srs=epsg4326),
+ ),
+ )
+ def test_polygon_str_roundtrip_verbose(
+ self,
+ id: typing.Optional[str],
+ target_id: typing.Optional[str],
+ extrude: typing.Optional[bool],
+ tessellate: typing.Optional[bool],
+ altitude_mode: typing.Optional[AltitudeMode],
+ geometry: typing.Optional[Polygon],
+ ) -> None:
+ polygon = fastkml.geometry.Polygon(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ _test_geometry_str_roundtrip_verbose(polygon)
diff --git a/tests/hypothesis/gx_test.py b/tests/hypothesis/gx_test.py
new file mode 100644
index 00000000..a9c184cd
--- /dev/null
+++ b/tests/hypothesis/gx_test.py
@@ -0,0 +1,141 @@
+# Copyright (C) 2024 Christian Ledermann
+#
+# This library is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This library is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+"""Test gx Track and MultiTrack."""
+import datetime
+import typing
+
+from hypothesis import given
+from hypothesis import strategies as st
+from hypothesis.extra.dateutil import timezones
+from pygeoif.hypothesis.strategies import epsg4326
+from pygeoif.hypothesis.strategies import points
+
+import fastkml
+import fastkml.enums
+import fastkml.gx
+import fastkml.types
+from fastkml.gx import Angle
+from fastkml.gx import TrackItem
+from fastkml.times import KmlDateTime
+from tests.base import Lxml
+from tests.hypothesis.common import assert_repr_roundtrip
+from tests.hypothesis.common import assert_str_roundtrip
+from tests.hypothesis.common import assert_str_roundtrip_terse
+from tests.hypothesis.common import assert_str_roundtrip_verbose
+from tests.hypothesis.strategies import nc_name
+
+track_items = st.builds(
+ TrackItem,
+ angle=st.one_of(
+ st.one_of(
+ st.builds(Angle),
+ st.builds(
+ Angle,
+ heading=st.floats(allow_nan=False, allow_infinity=False),
+ roll=st.floats(allow_nan=False, allow_infinity=False),
+ tilt=st.floats(allow_nan=False, allow_infinity=False),
+ ),
+ ),
+ ),
+ coord=points(srs=epsg4326),
+ when=st.builds(
+ KmlDateTime,
+ dt=st.datetimes(
+ allow_imaginary=False,
+ timezones=timezones(),
+ min_value=datetime.datetime(2000, 1, 1), # noqa: DTZ001
+ max_value=datetime.datetime(2050, 1, 1), # noqa: DTZ001
+ ),
+ ),
+)
+
+
+class TestGx(Lxml):
+
+ @given(
+ id=st.one_of(st.none(), nc_name()),
+ target_id=st.one_of(st.none(), nc_name()),
+ altitude_mode=st.one_of(st.none(), st.sampled_from(fastkml.enums.AltitudeMode)),
+ track_items=st.one_of(
+ st.none(),
+ st.lists(
+ track_items,
+ ),
+ ),
+ )
+ def test_fuzz_track_track_items(
+ self,
+ id: typing.Optional[str],
+ target_id: typing.Optional[str],
+ altitude_mode: typing.Optional[fastkml.enums.AltitudeMode],
+ track_items: typing.Optional[typing.Iterable[fastkml.gx.TrackItem]],
+ ) -> None:
+ track = fastkml.gx.Track(
+ id=id,
+ target_id=target_id,
+ altitude_mode=altitude_mode,
+ track_items=track_items,
+ )
+
+ assert_repr_roundtrip(track)
+ assert_str_roundtrip(track)
+ assert_str_roundtrip_terse(track)
+ assert_str_roundtrip_verbose(track)
+
+ @given(
+ id=st.one_of(st.none(), nc_name()),
+ target_id=st.one_of(st.none(), nc_name()),
+ altitude_mode=st.one_of(st.none(), st.sampled_from(fastkml.enums.AltitudeMode)),
+ tracks=st.one_of(
+ st.none(),
+ st.lists(
+ st.builds(
+ fastkml.gx.Track,
+ altitude_mode=st.one_of(
+ st.none(),
+ st.sampled_from(fastkml.enums.AltitudeMode),
+ ),
+ track_items=st.one_of(
+ st.none(),
+ st.lists(
+ track_items,
+ ),
+ ),
+ ),
+ ),
+ ),
+ interpolate=st.one_of(st.none(), st.booleans()),
+ )
+ def test_fuzz_multi_track(
+ self,
+ id: typing.Optional[str],
+ target_id: typing.Optional[str],
+ altitude_mode: typing.Optional[fastkml.enums.AltitudeMode],
+ tracks: typing.Optional[typing.Iterable[fastkml.gx.Track]],
+ interpolate: typing.Optional[bool],
+ ) -> None:
+ multi_track = fastkml.gx.MultiTrack(
+ id=id,
+ target_id=target_id,
+ altitude_mode=altitude_mode,
+ tracks=tracks,
+ interpolate=interpolate,
+ )
+
+ assert_repr_roundtrip(multi_track)
+ assert_str_roundtrip(multi_track)
+ assert_str_roundtrip_terse(multi_track)
+ assert_str_roundtrip_verbose(multi_track)
diff --git a/tests/hypothesis/links_test.py b/tests/hypothesis/links_test.py
new file mode 100644
index 00000000..b965783a
--- /dev/null
+++ b/tests/hypothesis/links_test.py
@@ -0,0 +1,101 @@
+# Copyright (C) 2024 Christian Ledermann
+#
+# This library is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This library is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+"""Test Link and Icon."""
+import string
+import typing
+from functools import partial
+
+import pytest
+from hypothesis import given
+from hypothesis import strategies as st
+from hypothesis.provisional import urls
+
+import fastkml
+import fastkml.enums
+from fastkml.validator import validate
+from tests.base import Lxml
+from tests.hypothesis.common import assert_repr_roundtrip
+from tests.hypothesis.common import assert_str_roundtrip
+from tests.hypothesis.common import assert_str_roundtrip_terse
+from tests.hypothesis.common import assert_str_roundtrip_verbose
+from tests.hypothesis.strategies import nc_name
+from tests.hypothesis.strategies import query_strings
+
+common_link = partial(
+ given,
+ id=st.one_of(st.none(), nc_name()),
+ target_id=st.one_of(st.none(), nc_name()),
+ href=st.one_of(st.none(), urls()),
+ refresh_mode=st.one_of(st.none(), st.sampled_from(fastkml.enums.RefreshMode)),
+ refresh_interval=st.one_of(
+ st.none(),
+ st.floats(allow_infinity=False, allow_nan=False),
+ ),
+ view_refresh_mode=st.one_of(
+ st.none(),
+ st.sampled_from(fastkml.enums.ViewRefreshMode),
+ ),
+ view_refresh_time=st.one_of(
+ st.none(),
+ st.floats(allow_infinity=False, allow_nan=False),
+ ),
+ view_bound_scale=st.one_of(
+ st.none(),
+ st.floats(allow_infinity=False, allow_nan=False),
+ ),
+ view_format=st.one_of(
+ st.none(),
+ st.text(string.ascii_letters + string.punctuation),
+ ),
+ http_query=st.one_of(st.none(), query_strings()),
+)
+
+
+class TestLxml(Lxml):
+
+ @pytest.mark.parametrize("cls", [fastkml.Link, fastkml.Icon])
+ @common_link()
+ def test_fuzz_link(
+ self,
+ cls: typing.Union[typing.Type[fastkml.Link], typing.Type[fastkml.Icon]],
+ id: typing.Optional[str],
+ target_id: typing.Optional[str],
+ href: typing.Optional[str],
+ refresh_mode: typing.Optional[fastkml.enums.RefreshMode],
+ refresh_interval: typing.Optional[float],
+ view_refresh_mode: typing.Optional[fastkml.enums.ViewRefreshMode],
+ view_refresh_time: typing.Optional[float],
+ view_bound_scale: typing.Optional[float],
+ view_format: typing.Optional[str],
+ http_query: typing.Optional[str],
+ ) -> None:
+ link = cls(
+ id=id,
+ target_id=target_id,
+ href=href,
+ refresh_mode=refresh_mode,
+ refresh_interval=refresh_interval,
+ view_refresh_mode=view_refresh_mode,
+ view_refresh_time=view_refresh_time,
+ view_bound_scale=view_bound_scale,
+ view_format=view_format,
+ http_query=http_query,
+ )
+ assert validate(element=link.etree_element())
+ assert_repr_roundtrip(link)
+ assert_str_roundtrip(link)
+ assert_str_roundtrip_terse(link)
+ assert_str_roundtrip_verbose(link)
diff --git a/tests/hypothesis/multi_geometry_test.py b/tests/hypothesis/multi_geometry_test.py
new file mode 100644
index 00000000..f54a56f6
--- /dev/null
+++ b/tests/hypothesis/multi_geometry_test.py
@@ -0,0 +1,704 @@
+# Copyright (C) 2024 Christian Ledermann
+#
+# This library is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This library is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""Property based tests of the Geometry classes."""
+from __future__ import annotations
+
+from functools import partial
+
+from hypothesis import given
+from hypothesis import settings
+from hypothesis import strategies as st
+from pygeoif.geometry import GeometryCollection
+from pygeoif.geometry import LinearRing
+from pygeoif.geometry import LineString
+from pygeoif.geometry import MultiLineString
+from pygeoif.geometry import MultiPoint
+from pygeoif.geometry import MultiPolygon
+from pygeoif.geometry import Point
+from pygeoif.geometry import Polygon
+from pygeoif.hypothesis.strategies import epsg4326
+from pygeoif.hypothesis.strategies import geometry_collections
+from pygeoif.hypothesis.strategies import multi_line_strings
+from pygeoif.hypothesis.strategies import multi_points
+from pygeoif.hypothesis.strategies import multi_polygons
+
+import fastkml.geometry
+from fastkml.enums import AltitudeMode
+from fastkml.enums import Verbosity
+from fastkml.validator import validate
+from tests.base import Lxml
+from tests.hypothesis.strategies import nc_name
+
+eval_locals = {
+ "Point": Point,
+ "Polygon": Polygon,
+ "LineString": LineString,
+ "LinearRing": LinearRing,
+ "AltitudeMode": AltitudeMode,
+ "MultiPoint": MultiPoint,
+ "MultiLineString": MultiLineString,
+ "MultiPolygon": MultiPolygon,
+ "GeometryCollection": GeometryCollection,
+ "fastkml": fastkml,
+}
+
+
+common_geometry = partial(
+ given,
+ id=st.one_of(st.none(), nc_name()),
+ target_id=st.one_of(st.none(), nc_name()),
+ extrude=st.one_of(st.none(), st.booleans()),
+ tessellate=st.one_of(st.none(), st.booleans()),
+ altitude_mode=st.one_of(
+ st.none(),
+ st.sampled_from(
+ (
+ AltitudeMode.absolute,
+ AltitudeMode.clamp_to_ground,
+ AltitudeMode.relative_to_ground,
+ ),
+ ),
+ ),
+)
+
+
+def _test_repr_roundtrip(
+ geometry: fastkml.geometry.MultiGeometry,
+ cls: type[MultiPoint | MultiLineString | MultiPolygon | GeometryCollection],
+) -> None:
+ new_g = eval(repr(geometry), {}, eval_locals) # noqa: S307
+
+ assert geometry == new_g
+ if geometry:
+ assert type(new_g.geometry) is cls
+ assert validate(element=new_g.etree_element())
+
+
+def _test_geometry_str_roundtrip(
+ geometry: fastkml.geometry.MultiGeometry,
+ *,
+ cls: type[MultiPoint | MultiLineString | MultiPolygon],
+ extrude: bool | None,
+ tessellate: bool | None,
+ altitude_mode: AltitudeMode | None,
+) -> None:
+ new_g = fastkml.geometry.MultiGeometry.from_string(geometry.to_string())
+
+ assert geometry.to_string() == new_g.to_string()
+ assert geometry == new_g
+ if not geometry:
+ return
+ assert new_g.geometry
+ assert geometry.geometry
+ assert type(new_g.geometry) is cls
+ for g1, g2 in zip(new_g.kml_geometries, geometry.kml_geometries):
+ assert g1.extrude == g2.extrude == extrude
+ assert g1.altitude_mode == g2.altitude_mode == altitude_mode
+ if not isinstance(g1, fastkml.geometry.Point):
+ assert g1.tessellate == g2.tessellate == tessellate
+ assert validate(element=new_g.etree_element())
+
+
+def _test_geometry_str_roundtrip_terse(
+ geometry: fastkml.geometry.MultiGeometry,
+ *,
+ cls: type[MultiPoint | MultiLineString | MultiPolygon],
+ extrude: bool | None,
+ tessellate: bool | None,
+ altitude_mode: AltitudeMode | None,
+) -> None:
+ new_g = fastkml.geometry.MultiGeometry.from_string(
+ geometry.to_string(verbosity=Verbosity.terse),
+ )
+
+ assert geometry.to_string(verbosity=Verbosity.verbose) == new_g.to_string(
+ verbosity=Verbosity.verbose,
+ )
+ if not geometry:
+ return
+ assert new_g.geometry
+ assert geometry.geometry
+ assert type(new_g.geometry) is cls
+ for new, orig in zip(new_g.kml_geometries, geometry.kml_geometries):
+ if extrude:
+ assert new.extrude == orig.extrude == extrude
+ else:
+ assert new.extrude is None
+ if altitude_mode == AltitudeMode.clamp_to_ground:
+ assert new.altitude_mode is None
+ else:
+ assert new.altitude_mode == orig.altitude_mode == altitude_mode
+ if not isinstance(new, fastkml.geometry.Point):
+ if tessellate:
+ assert new.tessellate == orig.tessellate == tessellate
+ else:
+ assert new.tessellate is None
+ assert validate(element=new_g.etree_element())
+
+
+def _test_geometry_str_roundtrip_verbose(
+ geometry: fastkml.geometry.MultiGeometry,
+ *,
+ cls: type[MultiPoint | MultiLineString | MultiPolygon],
+ extrude: bool | None,
+ tessellate: bool | None,
+ altitude_mode: AltitudeMode | None,
+) -> None:
+ new_g = fastkml.geometry.MultiGeometry.from_string(
+ geometry.to_string(verbosity=Verbosity.verbose),
+ )
+
+ assert geometry.to_string(verbosity=Verbosity.terse) == new_g.to_string(
+ verbosity=Verbosity.terse,
+ )
+ if not geometry:
+ return
+ assert new_g.geometry
+ assert geometry.geometry
+ assert type(new_g.geometry) is cls
+ for new, orig in zip(new_g.kml_geometries, geometry.kml_geometries):
+ if isinstance(new, fastkml.geometry.MultiGeometry):
+ continue
+ assert not isinstance(orig, fastkml.geometry.MultiGeometry)
+ if extrude:
+ assert new.extrude == orig.extrude == extrude
+ else:
+ assert new.extrude is False
+ if altitude_mode is None:
+ assert new.altitude_mode == AltitudeMode.clamp_to_ground
+ else:
+ assert new.altitude_mode == orig.altitude_mode == altitude_mode
+ if not isinstance(new, fastkml.geometry.Point):
+ if tessellate:
+ assert new.tessellate == orig.tessellate == tessellate
+ else:
+ assert new.tessellate is False
+ validate(element=new_g.etree_element())
+
+
+class TestLxml(Lxml):
+ """Validation requires lxml."""
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ multi_points(srs=epsg4326),
+ ),
+ )
+ @settings(deadline=1_000)
+ def test_multipoint_repr_roundtrip(
+ self,
+ id: str | None,
+ target_id: str | None,
+ extrude: bool | None,
+ tessellate: bool | None,
+ altitude_mode: AltitudeMode | None,
+ geometry: MultiPoint | None,
+ ) -> None:
+ multi_geometry = fastkml.geometry.MultiGeometry(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ _test_repr_roundtrip(multi_geometry, MultiPoint)
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ multi_points(srs=epsg4326),
+ ),
+ )
+ def test_multipoint_str_roundtrip(
+ self,
+ id: str | None,
+ target_id: str | None,
+ extrude: bool | None,
+ tessellate: bool | None,
+ altitude_mode: AltitudeMode | None,
+ geometry: MultiPoint | None,
+ ) -> None:
+ multi_geometry = fastkml.geometry.MultiGeometry(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ _test_geometry_str_roundtrip(
+ multi_geometry,
+ cls=MultiPoint,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ )
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ multi_points(srs=epsg4326),
+ ),
+ )
+ def test_multipoint_str_roundtrip_terse(
+ self,
+ id: str | None,
+ target_id: str | None,
+ extrude: bool | None,
+ tessellate: bool | None,
+ altitude_mode: AltitudeMode | None,
+ geometry: MultiPoint | None,
+ ) -> None:
+ multi_geometry = fastkml.geometry.MultiGeometry(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ _test_geometry_str_roundtrip_terse(
+ multi_geometry,
+ cls=MultiPoint,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ )
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ multi_points(srs=epsg4326),
+ ),
+ )
+ def test_multipoint_str_roundtrip_verbose(
+ self,
+ id: str | None,
+ target_id: str | None,
+ extrude: bool | None,
+ tessellate: bool | None,
+ altitude_mode: AltitudeMode | None,
+ geometry: MultiPoint | None,
+ ) -> None:
+ multi_geometry = fastkml.geometry.MultiGeometry(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ _test_geometry_str_roundtrip_verbose(
+ multi_geometry,
+ cls=MultiPoint,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ )
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ multi_line_strings(srs=epsg4326),
+ ),
+ )
+ def test_multilinestring_repr_roundtrip(
+ self,
+ id: str | None,
+ target_id: str | None,
+ extrude: bool | None,
+ tessellate: bool | None,
+ altitude_mode: AltitudeMode | None,
+ geometry: MultiLineString | None,
+ ) -> None:
+ multi_geometry = fastkml.geometry.MultiGeometry(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ _test_repr_roundtrip(multi_geometry, MultiLineString)
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ multi_line_strings(srs=epsg4326),
+ ),
+ )
+ def test_multilinestring_str_roundtrip(
+ self,
+ id: str | None,
+ target_id: str | None,
+ extrude: bool | None,
+ tessellate: bool | None,
+ altitude_mode: AltitudeMode | None,
+ geometry: MultiLineString | None,
+ ) -> None:
+ multi_geometry = fastkml.geometry.MultiGeometry(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ _test_geometry_str_roundtrip(
+ multi_geometry,
+ cls=MultiLineString,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ )
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ multi_line_strings(srs=epsg4326),
+ ),
+ )
+ def test_multilinestring_str_roundtrip_terse(
+ self,
+ id: str | None,
+ target_id: str | None,
+ extrude: bool | None,
+ tessellate: bool | None,
+ altitude_mode: AltitudeMode | None,
+ geometry: MultiLineString | None,
+ ) -> None:
+ multi_geometry = fastkml.geometry.MultiGeometry(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ _test_geometry_str_roundtrip_terse(
+ multi_geometry,
+ cls=MultiLineString,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ )
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ multi_line_strings(srs=epsg4326),
+ ),
+ )
+ def test_multilinestring_str_roundtrip_verbose(
+ self,
+ id: str | None,
+ target_id: str | None,
+ extrude: bool | None,
+ tessellate: bool | None,
+ altitude_mode: AltitudeMode | None,
+ geometry: MultiLineString | None,
+ ) -> None:
+ multi_geometry = fastkml.geometry.MultiGeometry(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ _test_geometry_str_roundtrip_verbose(
+ multi_geometry,
+ cls=MultiLineString,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ )
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ multi_polygons(srs=epsg4326),
+ ),
+ )
+ def test_multipolygon_repr_roundtrip(
+ self,
+ id: str | None,
+ target_id: str | None,
+ extrude: bool | None,
+ tessellate: bool | None,
+ altitude_mode: AltitudeMode | None,
+ geometry: MultiPolygon | None,
+ ) -> None:
+ multi_geometry = fastkml.geometry.MultiGeometry(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ _test_repr_roundtrip(multi_geometry, MultiPolygon)
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ multi_polygons(srs=epsg4326),
+ ),
+ )
+ def test_multipolygon_str_roundtrip(
+ self,
+ id: str | None,
+ target_id: str | None,
+ extrude: bool | None,
+ tessellate: bool | None,
+ altitude_mode: AltitudeMode | None,
+ geometry: MultiPolygon | None,
+ ) -> None:
+ multi_geometry = fastkml.geometry.MultiGeometry(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ _test_geometry_str_roundtrip(
+ multi_geometry,
+ cls=MultiPolygon,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ )
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ multi_polygons(srs=epsg4326),
+ ),
+ )
+ def test_multipolygon_str_roundtrip_terse(
+ self,
+ id: str | None,
+ target_id: str | None,
+ extrude: bool | None,
+ tessellate: bool | None,
+ altitude_mode: AltitudeMode | None,
+ geometry: MultiPolygon | None,
+ ) -> None:
+ multi_geometry = fastkml.geometry.MultiGeometry(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ _test_geometry_str_roundtrip_terse(
+ multi_geometry,
+ cls=MultiPolygon,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ )
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ multi_polygons(srs=epsg4326),
+ ),
+ )
+ def test_multipolygon_str_roundtrip_verbose(
+ self,
+ id: str | None,
+ target_id: str | None,
+ extrude: bool | None,
+ tessellate: bool | None,
+ altitude_mode: AltitudeMode | None,
+ geometry: MultiPolygon | None,
+ ) -> None:
+ multi_geometry = fastkml.geometry.MultiGeometry(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ _test_geometry_str_roundtrip_verbose(
+ multi_geometry,
+ cls=MultiPolygon,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ )
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ geometry_collections(srs=epsg4326),
+ ),
+ )
+ def test_geometrycollection_repr_roundtrip(
+ self,
+ id: str | None,
+ target_id: str | None,
+ extrude: bool | None,
+ tessellate: bool | None,
+ altitude_mode: AltitudeMode | None,
+ geometry: GeometryCollection | None,
+ ) -> None:
+ multi_geometry = fastkml.geometry.MultiGeometry(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ new_mg = eval(repr(multi_geometry), {}, eval_locals) # noqa: S307
+
+ assert multi_geometry == new_mg
+ if geometry:
+ assert isinstance(
+ new_mg.geometry,
+ (GeometryCollection, MultiPolygon, MultiLineString, MultiPoint),
+ )
+ else:
+ assert not new_mg
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ geometry_collections(srs=epsg4326),
+ ),
+ )
+ def test_geometrycollection_str_roundtrip(
+ self,
+ id: str | None,
+ target_id: str | None,
+ extrude: bool | None,
+ tessellate: bool | None,
+ altitude_mode: AltitudeMode | None,
+ geometry: GeometryCollection | None,
+ ) -> None:
+ multi_geometry = fastkml.geometry.MultiGeometry(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ new_mg = fastkml.geometry.MultiGeometry.from_string(
+ multi_geometry.to_string(),
+ )
+
+ if geometry:
+ assert isinstance(
+ new_mg.geometry,
+ (GeometryCollection, MultiPolygon, MultiLineString, MultiPoint),
+ )
+ else:
+ assert not new_mg
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ geometry_collections(srs=epsg4326),
+ ),
+ )
+ def test_geometrycollection_str_roundtrip_terse(
+ self,
+ id: str | None,
+ target_id: str | None,
+ extrude: bool | None,
+ tessellate: bool | None,
+ altitude_mode: AltitudeMode | None,
+ geometry: GeometryCollection | None,
+ ) -> None:
+ multi_geometry = fastkml.geometry.MultiGeometry(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ new_mg = fastkml.geometry.MultiGeometry.from_string(
+ multi_geometry.to_string(verbosity=Verbosity.terse),
+ )
+
+ if geometry:
+ assert isinstance(
+ new_mg.geometry,
+ (GeometryCollection, MultiPolygon, MultiLineString, MultiPoint),
+ )
+ else:
+ assert not new_mg
+
+ @common_geometry(
+ geometry=st.one_of(
+ st.none(),
+ geometry_collections(srs=epsg4326),
+ ),
+ )
+ def test_geometrycollection_str_roundtrip_verbose(
+ self,
+ id: str | None,
+ target_id: str | None,
+ extrude: bool | None,
+ tessellate: bool | None,
+ altitude_mode: AltitudeMode | None,
+ geometry: GeometryCollection | None,
+ ) -> None:
+ multi_geometry = fastkml.geometry.MultiGeometry(
+ id=id,
+ target_id=target_id,
+ extrude=extrude,
+ tessellate=tessellate,
+ altitude_mode=altitude_mode,
+ geometry=geometry,
+ )
+
+ new_mg = fastkml.geometry.MultiGeometry.from_string(
+ multi_geometry.to_string(verbosity=Verbosity.verbose),
+ )
+
+ if geometry:
+ assert isinstance(
+ new_mg.geometry,
+ (GeometryCollection, MultiPolygon, MultiLineString, MultiPoint),
+ )
+ else:
+ assert not new_mg
diff --git a/tests/hypothesis/strategies.py b/tests/hypothesis/strategies.py
new file mode 100644
index 00000000..3e154cea
--- /dev/null
+++ b/tests/hypothesis/strategies.py
@@ -0,0 +1,60 @@
+# Copyright (C) 2024 Christian Ledermann
+#
+# This library is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This library is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""Custom hypothesis strategies for testing."""
+import re
+import string
+from functools import partial
+from typing import Final
+from urllib.parse import urlencode
+
+from hypothesis import strategies as st
+
+ID_TEXT: Final = string.ascii_letters + string.digits + ".-_"
+nc_name = partial(
+ st.from_regex,
+ regex=re.compile(r"^[A-Za-z_][\w.-]*$"),
+ alphabet=ID_TEXT,
+ fullmatch=True,
+)
+
+href_langs = partial(
+ st.from_regex,
+ regex=re.compile(r"^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})?$"),
+ alphabet=f"{string.ascii_letters}-{string.digits}",
+ fullmatch=True,
+)
+media_types = partial(
+ st.from_regex,
+ regex=re.compile(r"^[a-zA-Z0-9-]+/[a-zA-Z0-9-]+$"),
+ alphabet=f"{string.ascii_letters}/-{string.digits}",
+ fullmatch=True,
+)
+xml_text = partial(
+ st.text,
+ alphabet=st.characters(min_codepoint=1, blacklist_categories=("Cc", "Cs")),
+)
+
+
+@st.composite
+def query_strings(draw: st.DrawFn) -> str:
+ params = draw(
+ st.dictionaries(
+ keys=st.text(alphabet=string.ascii_letters, min_size=1),
+ values=st.text(alphabet=string.printable),
+ ),
+ )
+ return urlencode(params)
diff --git a/tests/kml_test.py b/tests/kml_test.py
index e9f73188..e7189417 100644
--- a/tests/kml_test.py
+++ b/tests/kml_test.py
@@ -19,6 +19,7 @@
import pathlib
import pygeoif as geo
+from pygeoif.geometry import Polygon
from fastkml import containers
from fastkml import features
@@ -36,7 +37,7 @@ class TestStdLibrary(StdLibrary):
"""Test with the standard library."""
def test_linarring_placemark(self) -> None:
- doc = kml.KML.class_from_string(
+ doc = kml.KML.from_string(
"""
@@ -44,7 +45,7 @@ def test_linarring_placemark(self) -> None:
""",
)
- doc2 = kml.KML.class_from_string(doc.to_string())
+ doc2 = kml.KML.from_string(doc.to_string())
assert isinstance(doc.features[0].geometry, geo.LinearRing)
assert doc.to_string() == doc2.to_string()
@@ -56,7 +57,7 @@ def test_kml(self) -> None:
k.to_string().strip().replace(" ", "")
== ' '
)
- k2 = kml.KML.class_from_string(k.to_string(), ns="")
+ k2 = kml.KML.from_string(k.to_string(), ns="")
assert k.to_string() == k2.to_string()
def test_kml_with_document(self) -> None:
@@ -92,7 +93,7 @@ def test_kml_with_document(self) -> None:
""
""
)
- k = kml.KML.class_from_string(doc)
+ k = kml.KML.from_string(doc)
assert len(k.features) == 1
assert isinstance(k.features[0], Document)
assert len(k.features[0].features) == 1
@@ -174,6 +175,15 @@ def test_parse_kml_fileobject(self) -> None:
)
+class TestParseKMLNone(StdLibrary):
+ def test_kml_parse(self) -> None:
+ empty_placemark = KMLFILEDIR / "emptyPlacemarkWithoutId.xml"
+
+ doc = kml.KML.parse(file=empty_placemark, ns="None")
+
+ assert doc.ns == "None"
+
+
class TestLxml(Lxml, TestStdLibrary):
"""Test with lxml."""
@@ -192,3 +202,360 @@ def test_from_string_with_unbound_prefix(self) -> None:
k = kml.KML.parse(doc, ns="{http://www.opengis.net/kml/2.2}")
assert len(k.features) == 1
assert isinstance(k.features[0], features.Placemark)
+
+
+class TestKmlFromString:
+ def test_document(self) -> None:
+ doc = """
+
+ Document.kml
+ 1
+
+
+ Document Feature 1
+ #exampleStyleDocument
+
+ -122.371,37.816,0
+
+
+
+ Document Feature 2
+ #exampleStyleDocument
+
+ -122.370,37.817,0
+
+
+
+ """
+
+ k = kml.KML.from_string(doc)
+ assert len(k.features) == 1
+ assert len(k.features[0].features) == 2
+ k2 = kml.KML.from_string(k.to_string())
+ assert k.to_string() == k2.to_string()
+
+ def test_folders(self) -> None:
+ doc = """
+
+ Folder.kml
+ 1
+
+ A folder is a container that can hold multiple other objects
+
+
+ Folder object 1 (Placemark)
+
+ -122.377588,37.830266,0
+
+
+
+ Folder object 2 (Polygon)
+
+
+
+
+ -122.377830,37.830445,0
+ -122.377576,37.830631,0
+ -122.377840,37.830642,0
+ -122.377830,37.830445,0
+
+
+
+
+
+
+ Folder object 3 (Path)
+
+ 1
+
+ -122.378009,37.830128,0 -122.377885,37.830379,0
+
+
+
+
+ """
+
+ k = kml.KML.from_string(doc)
+ assert len(k.features) == 1
+ assert len(k.features[0].features) == 3
+ k2 = kml.KML.from_string(k.to_string())
+ assert k.to_string() == k2.to_string()
+
+ def test_placemark(self) -> None:
+ doc = """
+
+ Simple placemark
+ Attached to the ground. Intelligently places itself
+ at the height of the underlying terrain.
+
+ -122.0822035425683,37.42228990140251,0
+
+
+ """
+
+ k = kml.KML.from_string(doc)
+ assert len(k.features) == 1
+ assert k.features[0].name == "Simple placemark"
+ k2 = kml.KML.from_string(k.to_string())
+ assert k.to_string() == k2.to_string()
+
+ def test_polygon(self) -> None:
+ doc = """
+
+
+ South Africa
+
+
+
+
+ 31.521,-29.257,0
+ 31.326,-29.402,0
+ 30.902,-29.91,0
+ 30.623,-30.424,0
+ 30.056,-31.14,0
+ 28.926,-32.172,0
+ 28.22,-32.772,0
+ 27.465,-33.227,0
+ 26.419,-33.615,0
+ 25.91,-33.667,0
+ 25.781,-33.945,0
+ 25.173,-33.797,0
+ 24.678,-33.987,0
+ 23.594,-33.794,0
+ 22.988,-33.916,0
+ 22.574,-33.864,0
+ 21.543,-34.259,0
+ 20.689,-34.417,0
+ 20.071,-34.795,0
+ 19.616,-34.819,0
+ 19.193,-34.463,0
+ 18.855,-34.444,0
+ 18.425,-33.998,0
+ 18.377,-34.137,0
+ 18.244,-33.868,0
+ 18.25,-33.281,0
+ 17.925,-32.611,0
+ 18.248,-32.429,0
+ 18.222,-31.662,0
+ 17.567,-30.726,0
+ 17.064,-29.879,0
+ 17.063,-29.876,0
+ 16.345,-28.577,0
+ 16.824,-28.082,0
+ 17.219,-28.356,0
+ 17.387,-28.784,0
+ 17.836,-28.856,0
+ 18.465,-29.045,0
+ 19.002,-28.972,0
+ 19.895,-28.461,0
+ 19.896,-24.768,0
+ 20.166,-24.918,0
+ 20.759,-25.868,0
+ 20.666,-26.477,0
+ 20.89,-26.829,0
+ 21.606,-26.727,0
+ 22.106,-26.28,0
+ 22.58,-25.979,0
+ 22.824,-25.5,0
+ 23.312,-25.269,0
+ 23.734,-25.39,0
+ 24.211,-25.67,0
+ 25.025,-25.72,0
+ 25.665,-25.487,0
+ 25.766,-25.175,0
+ 25.942,-24.696,0
+ 26.486,-24.616,0
+ 26.786,-24.241,0
+ 27.119,-23.574,0
+ 28.017,-22.828,0
+ 29.432,-22.091,0
+ 29.839,-22.102,0
+ 30.323,-22.272,0
+ 30.66,-22.152,0
+ 31.191,-22.252,0
+ 31.67,-23.659,0
+ 31.931,-24.369,0
+ 31.752,-25.484,0
+ 31.838,-25.843,0
+ 31.333,-25.66,0
+ 31.044,-25.731,0
+ 30.95,-26.023,0
+ 30.677,-26.398,0
+ 30.686,-26.744,0
+ 31.283,-27.286,0
+ 31.868,-27.178,0
+ 32.072,-26.734,0
+ 32.83,-26.742,0
+ 32.58,-27.47,0
+ 32.462,-28.301,0
+ 32.203,-28.752,0
+ 31.521,-29.257,0
+
+
+
+
+
+
+ 28.978,-28.956,0
+ 28.542,-28.648,0
+ 28.074,-28.851,0
+ 27.533,-29.243,0
+ 26.999,-29.876,0
+ 27.749,-30.645,0
+ 28.107,-30.546,0
+ 28.291,-30.226,0
+ 28.848,-30.07,0
+ 29.018,-29.744,0
+ 29.325,-29.257,0
+ 28.978,-28.956,0
+
+
+
+
+
+ """
+
+ k = kml.KML.from_string(doc)
+ assert len(k.features) == 1
+ assert isinstance(k.features[0].geometry, Polygon)
+ k2 = kml.KML.from_string(k.to_string())
+ assert k.to_string() == k2.to_string()
+
+ def test_multipoints(self) -> None:
+ doc = """
+
+ MultiPoint
+ #stylesel_9
+
+
+ 16,-35,0.0
+
+
+ 16,-33,0.0
+
+
+ 16,-31,0.0
+
+
+ 16,-29,0.0
+
+
+ 16,-27,0.0
+
+
+ 16,-25,0.0
+
+
+ 16,-23,0.0
+
+
+ 16,-21,0.0
+
+
+ 18,-35,0.0
+
+
+ 18,-33,0.0
+
+
+ 18,-31,0.0
+
+
+ 18,-29,0.0
+
+
+ """
+
+ k = kml.KML.from_string(doc)
+ assert len(k.features) == 1
+ assert isinstance(k.features[0].geometry, geo.MultiPoint)
+ assert len(list(k.features[0].geometry.geoms)) == 12
+ k2 = kml.KML.from_string(k.to_string())
+ assert k.to_string() == k2.to_string()
+
+ def test_snippet(self) -> None:
+ doc = """
+
+ Short Desc
+ """
+
+ k = kml.KML.from_string(doc)
+ assert k.features[0].snippet.text == "Short Desc"
+ assert k.features[0].snippet.max_lines == 2
+ k.features[0].snippet = features.Snippet(
+ text="Another Snippet",
+ max_lines=3,
+ )
+ assert 'maxLines="3"' in k.to_string()
+ k.features[0].snippet = features.Snippet(text="Another Snippet")
+ assert "maxLines" not in k.to_string()
+ assert "Another Snippet" in k.to_string()
+ k.features[0].snippet = features.Snippet(text="Different Snippet")
+ assert "maxLines" not in k.to_string()
+ assert "Different Snippet" in k.to_string()
+ k.features[0].snippet = features.Snippet(text="", max_lines=4)
+ assert "Snippet" not in k.to_string()
+
+ def test_address(self) -> None:
+ doc = Document.from_string(
+ """
+
+ pm-name
+ pm-description
+ 1
+ 1600 Amphitheatre Parkway,...
+
+ """,
+ )
+
+ doc2 = Document.from_string(doc.to_string())
+ assert doc.to_string() == doc2.to_string()
+
+ def test_phone_number(self) -> None:
+ doc = Document.from_string(
+ """
+
+ pm-name
+ pm-description
+ 1
+ +1 234 567 8901
+
+ """,
+ )
+
+ doc2 = Document.from_string(doc.to_string())
+ assert doc.to_string() == doc2.to_string()
+
+ def test_groundoverlay(self) -> None:
+ doc = kml.KML.from_string(
+ """
+
+
+ Ground Overlays
+ Examples of ground overlays
+
+ Large-scale overlay on terrain
+ Overlay shows Mount Etna erupting
+ on July 13th, 2001.
+
+ http://developers.google.com/kml/etna.jpg
+
+
+ 37.91904192681665
+ 37.46543388598137
+ 15.35832653742206
+ 14.60128369746704
+ -0.1556640799496235
+
+
+
+
+ """,
+ )
+
+ doc2 = kml.KML.from_string(doc.to_string())
+ assert doc.to_string() == doc2.to_string()
diff --git a/tests/links_test.py b/tests/links_test.py
index 39486630..b7628f63 100644
--- a/tests/links_test.py
+++ b/tests/links_test.py
@@ -39,7 +39,7 @@ def test_icon(self) -> None:
http_query="clientName=fastkml",
)
- icon = links.Icon.class_from_string(icon_0.to_string())
+ icon = links.Icon.from_string(icon_0.to_string())
assert icon.id == "icon-01"
assert icon.href == "http://maps.google.com/mapfiles/kml/paddle/red-circle.png"
@@ -53,7 +53,7 @@ def test_icon(self) -> None:
def test_icon_read(self) -> None:
"""Test the Icon class."""
- icon = links.Icon.class_from_string(
+ icon = links.Icon.from_string(
"""
http://maps.google.com/mapfiles/kml/paddle/red-circle.png
@@ -78,7 +78,7 @@ def test_icon_read(self) -> None:
assert icon.view_format == "BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth]"
assert icon.http_query == "clientName=fastkml"
- icon2 = links.Icon.class_from_string(icon.to_string())
+ icon2 = links.Icon.from_string(icon.to_string())
assert icon2.to_string() == icon.to_string()
diff --git a/tests/ogc_conformance/data/kml/Document-places.kml b/tests/ogc_conformance/data/kml/Document-places.kml
index b35dd2ed..97129097 100644
--- a/tests/ogc_conformance/data/kml/Document-places.kml
+++ b/tests/ogc_conformance/data/kml/Document-places.kml
@@ -4,13 +4,13 @@
place123
- -95.44,40.42,0
+ -95.44,40.42,0.00
place456
- -95.43,40.42,0
+ -95.43,40.42,0.00
diff --git a/tests/oldunit_test.py b/tests/oldunit_test.py
deleted file mode 100644
index 80a56fa4..00000000
--- a/tests/oldunit_test.py
+++ /dev/null
@@ -1,1141 +0,0 @@
-# Copyright (C) 2012 -2022 Christian Ledermann
-#
-# This library is free software; you can redistribute it and/or modify it under
-# the terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation; either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# This library is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this library; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-import xml.etree.ElementTree
-
-from pygeoif.geometry import MultiPoint
-from pygeoif.geometry import Polygon
-
-from fastkml import atom
-from fastkml import config
-from fastkml import features
-from fastkml import kml
-from fastkml import kml_base
-from fastkml import styles
-from fastkml.enums import ColorMode
-from fastkml.enums import DisplayMode
-
-
-class TestBaseClasses:
- """
- BaseClasses must raise a NotImplementedError on etree_element.
- """
-
- def setup_method(self) -> None:
- """Always test with the same parser."""
- config.set_etree_implementation(xml.etree.ElementTree)
- config.set_default_namespaces()
-
- def test_base_object(self) -> None:
- bo = kml_base._BaseObject(id="id0")
- assert bo.id == "id0"
- assert bo.ns == config.KMLNS
- assert bo.target_id == ""
- bo.target_id = "target"
- assert bo.target_id == "target"
- bo.ns = ""
- bo.id = None
- assert bo.id is None
- assert not bo.ns
-
-
-class TestBuildKml:
- """Build a simple KML File."""
-
- def setup_method(self) -> None:
- """Always test with the same parser."""
- config.set_etree_implementation(xml.etree.ElementTree)
- config.set_default_namespaces()
-
- def test_folder(self) -> None:
- """KML file with folders."""
- ns = "{http://www.opengis.net/kml/2.2}"
- k = kml.KML(ns=ns)
- f = kml.Folder(ns=ns, id="id", name="name", description="description")
- nf = kml.Folder(
- ns=ns,
- id="nested-id",
- name="nested-name",
- description="nested-description",
- )
- f.append(nf)
- k.append(f)
- f2 = kml.Folder(ns, id="id2", name="name2", description="description2")
- k.append(f2)
- assert len(k.features) == 2
- assert len(k.features[0].features) == 1
- s = k.to_string()
- k2 = kml.KML.class_from_string(s, ns=ns)
- assert s == k2.to_string()
-
- def test_placemark(self) -> None:
- ns = "{http://www.opengis.net/kml/2.2}"
- k = kml.KML(ns=ns)
- p = kml.Placemark(ns, id="id", name="name", description="description")
- # XXX p.geometry = Point(0.0, 0.0, 0.0)
- p2 = kml.Placemark(ns, id="id2", name="name2", description="description2")
- # XXX p2.geometry = LineString([(0, 0, 0), (1, 1, 1)])
- k.append(p)
- k.append(p2)
- assert len(k.features) == 2
- k2 = kml.KML.class_from_string(k.to_string(prettyprint=True), ns=ns)
- assert k.to_string() == k2.to_string()
-
- def test_document(self) -> None:
- ns = "{http://www.opengis.net/kml/2.2}"
- k = kml.KML(ns=ns)
- d = kml.Document(ns, id="docid", name="doc name", description="doc description")
- f = kml.Folder(ns, id="fid", name="f name", description="f description")
- k.append(d)
- d.append(f)
- nf = kml.Folder(
- ns,
- id="nested-fid",
- name="nested f name",
- description="nested f description",
- )
- f.append(nf)
- f2 = kml.Folder(ns, id="id2", name="name2", description="description2")
- d.append(f2)
- p = kml.Placemark(ns, id="id", name="name", description="description")
- # XXX p.geometry = Polygon([(0, 0, 0), (1, 1, 0), (1, 0, 1)])
- p2 = kml.Placemark(ns, id="id2", name="name2", description="description2")
- # p2 does not have a geometry!
- f2.append(p)
- nf.append(p2)
- assert len(k.features) == 1
- assert len(k.features[0].features) == 2
- k2 = kml.KML.class_from_string(k.to_string())
- assert k.to_string() == k2.to_string()
-
- def test_author(self) -> None:
- d = kml.Document()
- d.atom_author = atom.Author(
- ns="{http://www.w3.org/2005/Atom}",
- name="Christian Ledermann",
- )
- assert "Christian Ledermann" in str(d.to_string())
- a = atom.Author(
- ns="{http://www.w3.org/2005/Atom}",
- name="Nobody",
- uri="http://localhost",
- email="cl@donotreply.com",
- )
- d.atom_author = a
- assert "Christian Ledermann" not in str(d.to_string())
- assert "Nobody" in str(d.to_string())
- assert "http://localhost" in str(d.to_string())
- assert "cl@donotreply.com" in str(d.to_string())
- d2 = kml.Document.class_from_string(d.to_string())
- assert d.to_string() == d2.to_string()
-
- def test_link(self) -> None:
- d = kml.Document()
- d.atom_link = atom.Link(ns=config.ATOMNS, href="http://localhost")
- assert "http://localhost" in str(d.to_string())
- d.atom_link = atom.Link(ns=config.ATOMNS, href="#here")
- assert "#here" in str(d.to_string())
- d2 = kml.Document.class_from_string(d.to_string())
- assert d.to_string() == d2.to_string()
-
- def test_address(self) -> None:
- address = "1600 Amphitheatre Parkway, Mountain View, CA 94043, USA"
- d = kml.Document()
- d.address = address
- assert address in str(d.to_string())
- assert "address>" in str(d.to_string())
-
- def test_phone_number(self) -> None:
- phone = "+1 234 567 8901"
- d = kml.Document()
- d.phone_number = phone
- assert phone in str(d.to_string())
- assert "phoneNumber>" in str(d.to_string())
-
-
-class TestKmlFromString:
- def test_document(self) -> None:
- doc = """
-
- Document.kml
- 1
-
-
- Document Feature 1
- #exampleStyleDocument
-
- -122.371,37.816,0
-
-
-
- Document Feature 2
- #exampleStyleDocument
-
- -122.370,37.817,0
-
-
-
- """
-
- k = kml.KML.class_from_string(doc)
- assert len(k.features) == 1
- assert len(k.features[0].features) == 2
- k2 = kml.KML.class_from_string(k.to_string())
- assert k.to_string() == k2.to_string()
-
- def test_folders(self) -> None:
- doc = """
-
- Folder.kml
- 1
-
- A folder is a container that can hold multiple other objects
-
-
- Folder object 1 (Placemark)
-
- -122.377588,37.830266,0
-
-
-
- Folder object 2 (Polygon)
-
-
-
-
- -122.377830,37.830445,0
- -122.377576,37.830631,0
- -122.377840,37.830642,0
- -122.377830,37.830445,0
-
-
-
-
-
-
- Folder object 3 (Path)
-
- 1
-
- -122.378009,37.830128,0 -122.377885,37.830379,0
-
-
-
-
- """
-
- k = kml.KML.class_from_string(doc)
- assert len(k.features) == 1
- assert len(k.features[0].features) == 3
- k2 = kml.KML.class_from_string(k.to_string())
- assert k.to_string() == k2.to_string()
-
- def test_placemark(self) -> None:
- doc = """
-
- Simple placemark
- Attached to the ground. Intelligently places itself
- at the height of the underlying terrain.
-
- -122.0822035425683,37.42228990140251,0
-
-
- """
-
- k = kml.KML.class_from_string(doc)
- assert len(k.features) == 1
- assert k.features[0].name == "Simple placemark"
- k2 = kml.KML.class_from_string(k.to_string())
- assert k.to_string() == k2.to_string()
-
- def test_polygon(self) -> None:
- doc = """
-
-
- South Africa
-
-
-
-
- 31.521,-29.257,0
- 31.326,-29.402,0
- 30.902,-29.91,0
- 30.623,-30.424,0
- 30.056,-31.14,0
- 28.926,-32.172,0
- 28.22,-32.772,0
- 27.465,-33.227,0
- 26.419,-33.615,0
- 25.91,-33.667,0
- 25.781,-33.945,0
- 25.173,-33.797,0
- 24.678,-33.987,0
- 23.594,-33.794,0
- 22.988,-33.916,0
- 22.574,-33.864,0
- 21.543,-34.259,0
- 20.689,-34.417,0
- 20.071,-34.795,0
- 19.616,-34.819,0
- 19.193,-34.463,0
- 18.855,-34.444,0
- 18.425,-33.998,0
- 18.377,-34.137,0
- 18.244,-33.868,0
- 18.25,-33.281,0
- 17.925,-32.611,0
- 18.248,-32.429,0
- 18.222,-31.662,0
- 17.567,-30.726,0
- 17.064,-29.879,0
- 17.063,-29.876,0
- 16.345,-28.577,0
- 16.824,-28.082,0
- 17.219,-28.356,0
- 17.387,-28.784,0
- 17.836,-28.856,0
- 18.465,-29.045,0
- 19.002,-28.972,0
- 19.895,-28.461,0
- 19.896,-24.768,0
- 20.166,-24.918,0
- 20.759,-25.868,0
- 20.666,-26.477,0
- 20.89,-26.829,0
- 21.606,-26.727,0
- 22.106,-26.28,0
- 22.58,-25.979,0
- 22.824,-25.5,0
- 23.312,-25.269,0
- 23.734,-25.39,0
- 24.211,-25.67,0
- 25.025,-25.72,0
- 25.665,-25.487,0
- 25.766,-25.175,0
- 25.942,-24.696,0
- 26.486,-24.616,0
- 26.786,-24.241,0
- 27.119,-23.574,0
- 28.017,-22.828,0
- 29.432,-22.091,0
- 29.839,-22.102,0
- 30.323,-22.272,0
- 30.66,-22.152,0
- 31.191,-22.252,0
- 31.67,-23.659,0
- 31.931,-24.369,0
- 31.752,-25.484,0
- 31.838,-25.843,0
- 31.333,-25.66,0
- 31.044,-25.731,0
- 30.95,-26.023,0
- 30.677,-26.398,0
- 30.686,-26.744,0
- 31.283,-27.286,0
- 31.868,-27.178,0
- 32.072,-26.734,0
- 32.83,-26.742,0
- 32.58,-27.47,0
- 32.462,-28.301,0
- 32.203,-28.752,0
- 31.521,-29.257,0
-
-
-
-
-
-
- 28.978,-28.956,0
- 28.542,-28.648,0
- 28.074,-28.851,0
- 27.533,-29.243,0
- 26.999,-29.876,0
- 27.749,-30.645,0
- 28.107,-30.546,0
- 28.291,-30.226,0
- 28.848,-30.07,0
- 29.018,-29.744,0
- 29.325,-29.257,0
- 28.978,-28.956,0
-
-
-
-
-
- """
-
- k = kml.KML.class_from_string(doc)
- assert len(k.features) == 1
- assert isinstance(k.features[0].geometry, Polygon)
- k2 = kml.KML.class_from_string(k.to_string())
- assert k.to_string() == k2.to_string()
-
- def test_multipoints(self) -> None:
- doc = """
-
- MultiPoint
- #stylesel_9
-
-
- 16,-35,0.0
-
-
- 16,-33,0.0
-
-
- 16,-31,0.0
-
-
- 16,-29,0.0
-
-
- 16,-27,0.0
-
-
- 16,-25,0.0
-
-
- 16,-23,0.0
-
-
- 16,-21,0.0
-
-
- 18,-35,0.0
-
-
- 18,-33,0.0
-
-
- 18,-31,0.0
-
-
- 18,-29,0.0
-
-
- """
-
- k = kml.KML.class_from_string(doc)
- assert len(k.features) == 1
- assert isinstance(k.features[0].geometry, MultiPoint)
- assert len(list(k.features[0].geometry.geoms)) == 12
- k2 = kml.KML.class_from_string(k.to_string())
- assert k.to_string() == k2.to_string()
-
- def test_atom(self) -> None:
- pass
-
- def test_snippet(self) -> None:
- doc = """
-
- Short Desc
- """
-
- k = kml.KML.class_from_string(doc)
- assert k.features[0].snippet.text == "Short Desc"
- assert k.features[0].snippet.max_lines == 2
- k.features[0].snippet = features.Snippet(
- text="Another Snippet",
- max_lines=3,
- )
- assert 'maxLines="3"' in k.to_string()
- k.features[0].snippet = features.Snippet(text="Another Snippet")
- assert "maxLines" not in k.to_string()
- assert "Another Snippet" in k.to_string()
- k.features[0].snippet = features.Snippet(text="Different Snippet")
- assert "maxLines" not in k.to_string()
- assert "Different Snippet" in k.to_string()
- k.features[0].snippet = features.Snippet(text="", max_lines=4)
- assert "Snippet" not in k.to_string()
-
- def test_address(self) -> None:
- doc = kml.Document.class_from_string(
- """
-
- pm-name
- pm-description
- 1
- 1600 Amphitheatre Parkway,...
-
- """,
- )
-
- doc2 = kml.Document.class_from_string(doc.to_string())
- assert doc.to_string() == doc2.to_string()
-
- def test_phone_number(self) -> None:
- doc = kml.Document.class_from_string(
- """
-
- pm-name
- pm-description
- 1
- +1 234 567 8901
-
- """,
- )
-
- doc2 = kml.Document.class_from_string(doc.to_string())
- assert doc.to_string() == doc2.to_string()
-
- def test_groundoverlay(self) -> None:
- doc = kml.KML.class_from_string(
- """
-
-
- Ground Overlays
- Examples of ground overlays
-
- Large-scale overlay on terrain
- Overlay shows Mount Etna erupting
- on July 13th, 2001.
-
- http://developers.google.com/kml/etna.jpg
-
-
- 37.91904192681665
- 37.46543388598137
- 15.35832653742206
- 14.60128369746704
- -0.1556640799496235
-
-
-
-
- """,
- )
-
- doc2 = kml.KML.class_from_string(doc.to_string())
- assert doc.to_string() == doc2.to_string()
-
-
-class TestStyle:
- def test_styleurl(self) -> None:
- f = kml.Document()
- s = styles.StyleUrl(config.KMLNS, url="#otherstyle")
- f.style_url = s
- f2 = kml.Document.class_from_string(f.to_string())
- assert f.to_string() == f2.to_string()
- assert isinstance(f2.style_url, styles.StyleUrl)
- assert f2.style_url.url == "#otherstyle"
-
- def test_style(self) -> None:
- lstyle = styles.LineStyle(color="red", width=2.0)
- style = styles.Style(styles=[lstyle])
- f = kml.Document(styles=[style])
- f2 = kml.Document.class_from_string(f.to_string(prettyprint=True))
- assert f.to_string() == f2.to_string()
-
- def test_polystyle_fill(self) -> None:
- styles.PolyStyle()
-
- def test_polystyle_outline(self) -> None:
- styles.PolyStyle()
-
-
-class TestStyleUsage:
- def test_create_document_style(self) -> None:
- style = styles.Style(
- styles=[
- styles.PolyStyle(
- color="7f000000",
- fill=True,
- outline=True,
- ),
- ],
- )
-
- doc = kml.Document(styles=[style])
-
- doc2 = kml.Document()
- doc2.styles.append(style)
-
- expected = """
-
-
-
-
- 7f000000
- 1
- 1
-
-
-
- """
-
- doc3 = kml.Document.class_from_string(expected)
-
- assert doc.to_string() == doc2.to_string()
- assert doc2.to_string() == doc3.to_string()
- assert doc.to_string() == doc3.to_string()
-
- def test_create_placemark_style(self) -> None:
- style = styles.Style(
- styles=[
- styles.PolyStyle(
- color="7f000000",
- fill=True,
- outline=True,
- ),
- ],
- )
-
- place = kml.Placemark(styles=[style])
-
- place2 = kml.Placemark()
- place2.styles.append(style)
-
- expected = """
-
-
-
- 7f000000
- 1
- 1
-
-
-
- """
-
- place3 = kml.Placemark.class_from_string(expected)
- assert place.to_string() == place2.to_string()
- assert place2.to_string() == place3.to_string()
- assert place.to_string() == place3.to_string()
-
-
-class TestStyleFromString:
- def test_styleurl(self) -> None:
- doc = """
-
- Document.kml
- 1
- #default
-
- """
-
- k = kml.KML.class_from_string(doc)
- assert len(k.features) == 1
- assert k.features[0].style_url.url == "#default"
- k2 = kml.KML.class_from_string(k.to_string())
- assert k.to_string() == k2.to_string()
-
- def test_balloonstyle(self) -> None:
- doc = """
-
- Document.kml
-
-
- """
-
- k = kml.KML.class_from_string(doc)
- features = k.features
- assert len(features) == 1
- style = features[0].styles[0]
- assert isinstance(style, styles.Style)
- style_1 = style.styles[0]
- assert isinstance(style_1, styles.BalloonStyle)
- assert style_1.bg_color == "ffffffbb"
- assert style_1.text_color == "ff000000"
- assert style_1.display_mode == DisplayMode.default
- assert style_1.text
- assert "$[geDirections]" in style_1.text
- assert "$[description]" in style_1.text
- k2 = kml.KML.class_from_string(k.to_string())
- assert k2.to_string() == k.to_string()
-
- def test_balloonstyle_old_color(self) -> None:
- doc = """
-
- Document.kml
-
-
- """
-
- k = kml.KML.class_from_string(doc)
- assert len(k.features) == 1
- assert isinstance(k.features[0].styles[0], styles.Style)
- style = k.features[0].styles[0].styles[0]
- assert isinstance(style, styles.BalloonStyle)
- assert style.bg_color is None
- assert not style
- k2 = kml.KML.class_from_string(k.to_string())
- assert k2.to_string() == k.to_string()
-
- def test_labelstyle(self) -> None:
- doc = """
-
- Document.kml
- 1
-
-
- """
-
- k = kml.KML.class_from_string(doc)
- assert len(k.features) == 1
- assert isinstance(k.features[0].styles[0], styles.Style)
- style = k.features[0].styles[0].styles[0]
- assert isinstance(style, styles.LabelStyle)
- assert style.color == "ff0000cc"
- assert style.color_mode is None
- k2 = kml.KML.class_from_string(k.to_string())
- assert k.to_string() == k2.to_string()
-
- def test_iconstyle(self) -> None:
- doc = """
-
-
-
- """
-
- k = kml.KML.class_from_string(doc)
- assert len(k.features) == 1
- assert isinstance(k.features[0].styles[0], styles.Style)
- style = k.features[0].styles[0].styles[0]
- assert isinstance(style, styles.IconStyle)
- assert style.color == "ff00ff00"
- assert style.scale == 1.1
- assert style.color_mode == ColorMode.random
- assert style.heading == 0.0
- assert style.icon.href == "http://maps.google.com/icon21.png"
- k2 = kml.KML.class_from_string(k.to_string())
- assert k.to_string() == k2.to_string()
-
- def test_linestyle(self) -> None:
- doc = """
-
- LineStyle.kml
- 1
-
-
- """
-
- k = kml.KML.class_from_string(doc)
- assert len(k.features) == 1
- assert isinstance(k.features[0].styles[0], styles.Style)
- style = k.features[0].styles[0].styles[0]
- assert isinstance(style, styles.LineStyle)
- assert style.color == "7f0000ff"
- assert style.width == 4
- k2 = kml.KML.class_from_string(k.to_string())
- assert k.to_string() == k2.to_string()
-
- def test_polystyle(self) -> None:
- doc = """
-
- PolygonStyle.kml
- 1
-
-
- """
-
- # XXX fill and outline
- k = kml.KML.class_from_string(doc)
- assert len(k.features) == 1
- assert isinstance(k.features[0].styles[0], styles.Style)
- style = k.features[0].styles[0].styles[0]
- assert isinstance(style, styles.PolyStyle)
- assert style.color == "ff0000cc"
- assert style.color_mode == ColorMode.random
- k2 = kml.KML.class_from_string(k.to_string())
- assert k.to_string() == k2.to_string()
-
- def test_polystyle_boolean_fill(self) -> None:
- doc = """
-
- PolygonStyle.kml
- 1
-
-
- """
-
- k = kml.KML.class_from_string(doc, strict=False)
- style = k.features[0].styles[0].styles[0]
- assert isinstance(style, styles.PolyStyle)
- assert style.fill == 0
- k2 = kml.KML.class_from_string(k.to_string())
- assert k.to_string() == k2.to_string()
-
- def test_polystyle_boolean_outline(self) -> None:
- doc = """
-
- PolygonStyle.kml
- 1
-
-
- """
-
- k = kml.KML.class_from_string(doc, strict=False)
- style = k.features[0].styles[0].styles[0]
- assert isinstance(style, styles.PolyStyle)
- assert style.outline == 0
- k2 = kml.KML.class_from_string(k.to_string())
- assert k.to_string() == k2.to_string()
-
- def test_polystyle_float_fill(self) -> None:
- doc = """
-
- PolygonStyle.kml
- 1
-
-
- """
-
- k = kml.KML.class_from_string(doc, strict=False)
- style = k.features[0].styles[0].styles[0]
- assert isinstance(style, styles.PolyStyle)
- assert style.fill == 0
- k2 = kml.KML.class_from_string(k.to_string())
- assert k.to_string() == k2.to_string()
-
- def test_polystyle_float_outline(self) -> None:
- doc = """
-
- PolygonStyle.kml
- 1
-
-
- """
-
- k = kml.KML.class_from_string(doc, strict=False)
- style = k.features[0].styles[0].styles[0]
- assert isinstance(style, styles.PolyStyle)
- assert style.outline == 0
- k2 = kml.KML.class_from_string(k.to_string())
- assert k.to_string() == k2.to_string()
-
- def test_styles(self) -> None:
- doc = """
-
-
-
-
-
- """
-
- k = kml.KML.class_from_string(doc)
- assert len(k.features) == 1
- assert isinstance(k.features[0].styles[0], styles.Style)
- style = k.features[0].styles[0].styles
- assert len(style) == 4
- k2 = kml.KML.class_from_string(k.to_string())
- assert k.to_string() == k2.to_string()
-
- def test_stylemapurl(self) -> None:
- doc = """
-
-
-
- normal
- #normalState
-
-
- highlight
- #highlightState
-
-
-
- """
-
- k = kml.KML.class_from_string(doc)
- features = k.features
- assert len(features) == 1
- feature_styles = features[0].styles
- assert isinstance(
- feature_styles[0],
- styles.StyleMap,
- )
- sm = feature_styles[0]
-
- assert isinstance(sm.normal, styles.StyleUrl)
- assert sm.normal.url == "#normalState"
- assert isinstance(sm.highlight, styles.StyleUrl)
- assert sm.highlight.url == "#highlightState"
- k2 = kml.KML.class_from_string(k.to_string())
- assert k.to_string() == k2.to_string()
-
- def test_stylemapstyles(self) -> None:
- doc = """
-
-
-
- normal
-
-
-
- highlight
-
-
-
-
- """
-
- k = kml.KML.class_from_string(doc)
- assert len(k.features) == 1
- assert isinstance(
- k.features[0].styles[0],
- styles.StyleMap,
- )
- sm = k.features[0].styles[0]
- assert isinstance(sm.normal, styles.Style)
- assert len(sm.normal.styles) == 1
- assert isinstance(sm.normal.styles[0], styles.LabelStyle)
- assert isinstance(sm.highlight, styles.Style)
- assert isinstance(sm.highlight, styles.Style)
- assert len(sm.highlight.styles) == 2
- assert isinstance(sm.highlight.styles[0], styles.LineStyle)
- assert isinstance(sm.highlight.styles[1], styles.PolyStyle)
- k2 = kml.KML.class_from_string(k.to_string())
- assert k.to_string() == k2.to_string()
-
- def test_get_style_by_url(self) -> None:
- doc = """
-
- Document.kml
- 1
-
-
-
- normal
- #normalState
-
-
- highlight
- #highlightState
-
-
-
-
- """
-
- k = kml.KML.class_from_string(doc)
- assert len(k.features) == 1
- document = k.features[0]
- style = document.get_style_by_url(
- "http://localhost:8080/somepath#exampleStyleDocument",
- )
- assert isinstance(style.styles[0], styles.LabelStyle)
- style = document.get_style_by_url("somepath#linestyleExample")
- assert isinstance(style.styles[0], styles.LineStyle)
- style = document.get_style_by_url("#styleMapExample")
- assert isinstance(style, styles.StyleMap)
-
-
-def test_nested_multigeometry() -> None:
- doc = """
-
-
-
-
-
- -122.366278,37.818844,0 -122.365248,37.819267,0
- -122.365640,37.819875,0 -122.366278,37.818844,0
-
-
-
-
-
- -122.365,37.819,0
-
-
-
-
- -122.365278,37.819000,0 -122.365248,37.819267,0
-
-
-
-
-
-
- -122.365248,37.819267,0 -122.365640,37.819875,0
- -122.366278,37.818844,0 -122.365248,37.819267,0
-
-
-
-
-
-
-
- """
-
- k = kml.KML.class_from_string(doc)
- placemark = k.features[0].features[0]
-
- first_multigeometry = placemark.geometry
- assert len(list(first_multigeometry.geoms)) == 3
-
- second_multigeometry = next(
- g for g in first_multigeometry.geoms if g.geom_type == "GeometryCollection"
- )
- assert len(list(second_multigeometry.geoms)) == 2
-
-
-class TestGetGeometry:
- def test_nested_multigeometry(self) -> None:
- doc = """
-
-
-
-
-
- -122.366278,37.818844,0 -122.365248,37.819267,0
- -122.365640,37.819875,0 -122.366278,37.818844,0
-
-
-
-
-
- -122.365,37.819,0
-
-
-
-
- -122.365278,37.819000,0 -122.365248,37.819267,0
-
-
-
-
-
-
- -122.365248,37.819267,0 -122.365640,37.819875,0
- -122.366278,37.818844,0 -122.365248,37.819267,0
-
-
-
-
-
-
-
- """
-
- k = kml.KML.class_from_string(doc)
- placemark = k.features[0].features[0]
-
- first_multigeometry = placemark.geometry
- assert len(list(first_multigeometry.geoms)) == 3
-
- second_multigeometry = next(
- g for g in first_multigeometry.geoms if g.geom_type == "GeometryCollection"
- )
- assert len(list(second_multigeometry.geoms)) == 2
diff --git a/tests/overlays_test.py b/tests/overlays_test.py
index a21c2339..3a7a71cf 100644
--- a/tests/overlays_test.py
+++ b/tests/overlays_test.py
@@ -36,7 +36,7 @@ class TestGroundOverlayString(StdLibrary):
def test_default_to_string(self) -> None:
g = overlays.GroundOverlay()
- expected = overlays.GroundOverlay.class_from_string(
+ expected = overlays.GroundOverlay.from_string(
''
" ",
)
@@ -49,7 +49,7 @@ def test_to_string(self) -> None:
g.draw_order = 1
g.color = "00010203"
- expected = overlays.GroundOverlay.class_from_string(
+ expected = overlays.GroundOverlay.from_string(
''
"00010203 "
"1 "
@@ -65,7 +65,7 @@ def test_altitude_from_int(self) -> None:
g = overlays.GroundOverlay()
g.altitude = 123.0
- expected = overlays.GroundOverlay.class_from_string(
+ expected = overlays.GroundOverlay.from_string(
''
"123 "
" ",
@@ -77,7 +77,7 @@ def test_altitude_from_float(self) -> None:
g = overlays.GroundOverlay()
g.altitude = 123.4
- expected = overlays.GroundOverlay.class_from_string(
+ expected = overlays.GroundOverlay.from_string(
''
"123.4 "
" ",
@@ -86,7 +86,7 @@ def test_altitude_from_float(self) -> None:
assert g.to_string() == expected.to_string()
def test_altitude_invalid(self) -> None:
- g = overlays.GroundOverlay.class_from_string(
+ g = overlays.GroundOverlay.from_string(
''
" one two "
" ",
@@ -96,7 +96,7 @@ def test_altitude_invalid(self) -> None:
assert g.altitude is None
def test_draw_order_from_invalid(self) -> None:
- g = overlays.GroundOverlay.class_from_string(
+ g = overlays.GroundOverlay.from_string(
''
"nan "
" ",
@@ -111,7 +111,7 @@ def test_altitude_from_string(self) -> None:
altitude_mode=AltitudeMode.clamp_to_ground,
)
- expected = overlays.GroundOverlay.class_from_string(
+ expected = overlays.GroundOverlay.from_string(
''
"123.4 "
"clampToGround "
@@ -122,7 +122,7 @@ def test_altitude_from_string(self) -> None:
def test_altitude_mode_absolute(self) -> None:
g = overlays.GroundOverlay(altitude=123.4, altitude_mode=AltitudeMode.absolute)
- expected = overlays.GroundOverlay.class_from_string(
+ expected = overlays.GroundOverlay.from_string(
''
"123.4 "
"absolute "
@@ -141,7 +141,7 @@ def test_latlonbox_no_rotation(self) -> None:
)
g = overlays.GroundOverlay(lat_lon_box=llb)
- expected = overlays.GroundOverlay.class_from_string(
+ expected = overlays.GroundOverlay.from_string(
''
""
"10 "
@@ -165,7 +165,7 @@ def test_latlonbox_rotation(self) -> None:
)
g = overlays.GroundOverlay(lat_lon_box=llb)
- expected = overlays.GroundOverlay.class_from_string(
+ expected = overlays.GroundOverlay.from_string(
''
""
"10 "
@@ -190,7 +190,7 @@ def test_latlonbox_nswer(self) -> None:
)
g = overlays.GroundOverlay()
g.lat_lon_box = llb
- expected = overlays.GroundOverlay.class_from_string(
+ expected = overlays.GroundOverlay.from_string(
''
""
"10 "
@@ -249,6 +249,9 @@ def test_create_photo_overlay_with_all_optional_parameters(self) -> None:
assert photo_overlay.description == "This is a photo overlay"
assert photo_overlay.shape == enums.Shape.rectangle
assert photo_overlay.rotation == 0
+ assert photo_overlay.view_volume.__bool__() is True
+ assert bool(photo_overlay.view_volume)
+ assert bool(photo_overlay.image_pyramid)
def test_read_photo_overlay(self) -> None:
"""Read a PhotoOverlay object from a KML file."""
@@ -275,7 +278,7 @@ def test_read_photo_overlay(self) -> None:
"rectangle "
)
- p_overlay = overlays.PhotoOverlay.class_from_string(doc)
+ p_overlay = overlays.PhotoOverlay.from_string(doc)
assert p_overlay.id == "photo_overlay_1"
assert p_overlay.name == "Photo Overlay"
@@ -329,7 +332,7 @@ def test_camera_altitude_none(self) -> None:
def test_camera_altitude_mode_default(self) -> None:
po = overlays.PhotoOverlay(view=views.Camera())
- assert po.view.altitude_mode == AltitudeMode("relativeToGround")
+ assert po.view.altitude_mode is None
def test_camera_altitude_mode_clamp(self) -> None:
po = overlays.PhotoOverlay(view=views.Camera())
@@ -357,7 +360,6 @@ def test_camera_initialization(self) -> None:
assert po.view.heading == 40
assert po.view.tilt == 50
assert po.view.roll == 60
- assert po.view.altitude_mode == AltitudeMode("relativeToGround")
class TestGroundOverlayLxml(Lxml, TestGroundOverlay):
diff --git a/tests/registry_test.py b/tests/registry_test.py
index 26ac2cb0..083b3fcc 100644
--- a/tests/registry_test.py
+++ b/tests/registry_test.py
@@ -20,7 +20,6 @@
from typing import Optional
from typing import Tuple
from typing import Type
-from typing import Union
from fastkml.base import _XMLObject
from fastkml.enums import Verbosity
@@ -28,15 +27,6 @@
from fastkml.registry import RegistryItem
from fastkml.types import Element
-known_types = Union[
- Type[_XMLObject],
- Type[Enum],
- Type[bool],
- Type[int],
- Type[str],
- Type[float],
-]
-
class A(_XMLObject):
"""A test class."""
@@ -66,6 +56,7 @@ def set_element(
node_name: str,
precision: Optional[int],
verbosity: Optional[Verbosity],
+ default: Any,
) -> None:
"""Get an attribute from an XML object."""
@@ -77,7 +68,7 @@ def get_kwarg( # type: ignore[empty-body]
name_spaces: Dict[str, str],
node_name: str,
kwarg: str,
- classes: Tuple[known_types, ...],
+ classes: Tuple[Type[object], ...],
strict: bool,
) -> Dict[str, Any]:
"""Get the kwarg for the constructor from the element."""
diff --git a/tests/repr_eq_test.py b/tests/repr_eq_test.py
index 5f88e0c0..65d827bf 100644
--- a/tests/repr_eq_test.py
+++ b/tests/repr_eq_test.py
@@ -1946,7 +1946,7 @@ def test_str(self) -> None:
def test_eq_str_round_trip(self) -> None:
"""Test the equality of the original and the round-tripped document."""
- new_doc = fastkml.KML.class_from_string(self.clean_doc.to_string(precision=15))
+ new_doc = fastkml.KML.from_string(self.clean_doc.to_string(precision=15))
assert str(self.clean_doc) == str(new_doc)
assert repr(new_doc) == repr(self.clean_doc)
diff --git a/tests/styles_test.py b/tests/styles_test.py
index 144bdc97..e8c2e348 100644
--- a/tests/styles_test.py
+++ b/tests/styles_test.py
@@ -19,11 +19,13 @@
from fastkml import links
from fastkml import styles
+from fastkml.containers import Document
from fastkml.enums import ColorMode
from fastkml.enums import DisplayMode
from fastkml.enums import PairKey
from fastkml.enums import Units
from fastkml.exceptions import KMLParseError
+from fastkml.features import Placemark
from tests.base import Lxml
from tests.base import StdLibrary
@@ -42,7 +44,7 @@ def test_style_url(self) -> None:
assert ">#style-0" in serialized
def test_style_url_read(self) -> None:
- url = styles.StyleUrl.class_from_string(
+ url = styles.StyleUrl.from_string(
'#style-0 ',
)
@@ -111,7 +113,7 @@ def test_icon_style_with_hot_spot(self) -> None:
assert "href" not in serialized
def test_icon_style_read(self) -> None:
- icons = styles.IconStyle.class_from_string(
+ icons = styles.IconStyle.from_string(
''
"ff2200ff random "
@@ -134,7 +136,7 @@ def test_icon_style_read(self) -> None:
assert icons.hot_spot.yunits.value == "insetPixels"
def test_icon_style_with_hot_spot_enum_relaxed(self) -> None:
- icons = styles.IconStyle.class_from_string(
+ icons = styles.IconStyle.from_string(
''
"ff2200ff random "
@@ -149,7 +151,7 @@ def test_icon_style_with_hot_spot_enum_relaxed(self) -> None:
def test_icon_style_with_hot_spot_enum_strict(self) -> None:
with pytest.raises(KMLParseError):
- styles.IconStyle.class_from_string(
+ styles.IconStyle.from_string(
''
"ff2200ff random "
@@ -179,7 +181,7 @@ def test_line_style(self) -> None:
assert "" in serialized
def test_line_style_read(self) -> None:
- lines = styles.LineStyle.class_from_string(
+ lines = styles.LineStyle.from_string(
'\n'
" ffaa00ff \n"
@@ -216,7 +218,7 @@ def test_poly_style(self) -> None:
assert "" in serialized
def test_poly_style_read(self) -> None:
- ps = styles.PolyStyle.class_from_string(
+ ps = styles.PolyStyle.from_string(
''
"ffaabbff "
@@ -255,7 +257,7 @@ def test_label_style(self) -> None:
assert "" in serialized
def test_label_style_read(self) -> None:
- ls = styles.LabelStyle.class_from_string(
+ ls = styles.LabelStyle.from_string(
''
"ff001122 "
@@ -294,7 +296,7 @@ def test_balloon_style(self) -> None:
assert "" in serialized
def test_balloon_style_read(self) -> None:
- bs = styles.BalloonStyle.class_from_string(
+ bs = styles.BalloonStyle.from_string(
''
"7fff1144 "
@@ -374,7 +376,7 @@ def test_style(self) -> None:
assert "" in serialized
def test_style_read(self) -> None:
- style = styles.Style.class_from_string(
+ style = styles.Style.from_string(
''
''
@@ -555,7 +557,7 @@ def test_stylemap(self) -> None: # noqa: PLR0915
assert "" in serialized
def test_stylemap_read(self) -> None:
- sm = styles.StyleMap.class_from_string(
+ sm = styles.StyleMap.from_string(
"""
@@ -610,6 +612,82 @@ def test_stylemap_read(self) -> None:
assert sm.highlight.id == "id-u0"
assert sm.highlight.target_id == "target-u0"
+ def test_style_map_none_case(self) -> None:
+ sm = styles.StyleMap()
+
+ assert sm.normal is None
+ assert sm.highlight is None
+
+
+class TestStyleUsage:
+ def test_create_document_style(self) -> None:
+ style = styles.Style(
+ styles=[
+ styles.PolyStyle(
+ color="7f000000",
+ fill=True,
+ outline=True,
+ ),
+ ],
+ )
+
+ doc = Document(styles=[style])
+
+ doc2 = Document()
+ doc2.styles.append(style)
+
+ expected = """
+
+
+
+
+ 7f000000
+ 1
+ 1
+
+
+
+ """
+
+ doc3 = Document.from_string(expected)
+
+ assert doc.to_string() == doc2.to_string()
+ assert doc2.to_string() == doc3.to_string()
+ assert doc.to_string() == doc3.to_string()
+
+ def test_create_placemark_style(self) -> None:
+ style = styles.Style(
+ styles=[
+ styles.PolyStyle(
+ color="7f000000",
+ fill=True,
+ outline=True,
+ ),
+ ],
+ )
+
+ place = Placemark(styles=[style])
+
+ place2 = Placemark()
+ place2.styles.append(style)
+
+ expected = """
+
+
+
+ 7f000000
+ 1
+ 1
+
+
+
+ """
+
+ place3 = Placemark.from_string(expected)
+ assert place.to_string() == place2.to_string()
+ assert place2.to_string() == place3.to_string()
+ assert place.to_string() == place3.to_string()
+
class TestLxml(Lxml, TestStdLibrary):
"""Test with lxml."""
diff --git a/tests/times_test.py b/tests/times_test.py
index e32ca6a1..1876a003 100644
--- a/tests/times_test.py
+++ b/tests/times_test.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2022 - 2023 Christian Ledermann
+# Copyright (C) 2022 - 2024 Christian Ledermann
#
# This library is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
@@ -32,7 +32,7 @@ class TestDateTime(StdLibrary):
"""KmlDateTime implementation is independent of XML parser."""
def test_kml_datetime_year(self) -> None:
- dt = datetime.datetime(2000, 1, 1)
+ dt = datetime.datetime(2000, 1, 1) # noqa: DTZ001
kdt = KmlDateTime(dt, DateTimeResolution.year)
@@ -41,7 +41,7 @@ def test_kml_datetime_year(self) -> None:
assert bool(kdt)
def test_kml_datetime_year_month(self) -> None:
- dt = datetime.datetime(2000, 3, 1)
+ dt = datetime.datetime(2000, 3, 1) # noqa: DTZ001
kdt = KmlDateTime(dt, DateTimeResolution.year_month)
@@ -50,7 +50,7 @@ def test_kml_datetime_year_month(self) -> None:
assert bool(kdt)
def test_kml_datetime_date(self) -> None:
- dt = datetime.datetime.now()
+ dt = datetime.datetime.now() # noqa: DTZ005
kdt = KmlDateTime(dt, DateTimeResolution.date)
@@ -59,7 +59,7 @@ def test_kml_datetime_date(self) -> None:
assert bool(kdt)
def test_kml_datetime_date_implicit(self) -> None:
- dt = datetime.date.today()
+ dt = datetime.date.today() # noqa: DTZ011
kdt = KmlDateTime(dt)
@@ -68,7 +68,7 @@ def test_kml_datetime_date_implicit(self) -> None:
assert bool(kdt)
def test_kml_datetime_datetime(self) -> None:
- dt = datetime.datetime.now()
+ dt = datetime.datetime.now() # noqa: DTZ005
kdt = KmlDateTime(dt, DateTimeResolution.datetime)
@@ -77,7 +77,7 @@ def test_kml_datetime_datetime(self) -> None:
assert bool(kdt)
def test_kml_datetime_datetime_implicit(self) -> None:
- dt = datetime.datetime.now()
+ dt = datetime.datetime.now() # noqa: DTZ005
kdt = KmlDateTime(dt)
@@ -91,64 +91,80 @@ def test_kml_datetime_no_datetime(self) -> None:
assert kdt.resolution == DateTimeResolution.date
assert not bool(kdt)
- with pytest.raises(AttributeError):
+ with pytest.raises(
+ AttributeError,
+ match="^'NoneType' object has no attribute 'isoformat'$",
+ ):
str(kdt)
def test_parse_year(self) -> None:
dt = KmlDateTime.parse("2000")
+ assert dt
assert dt.resolution == DateTimeResolution.year
assert dt.dt == datetime.datetime(2000, 1, 1, tzinfo=tzutc())
def test_parse_year_0(self) -> None:
- with pytest.raises(ValueError):
+ with pytest.raises(
+ ValueError,
+ match="^year 0 is out of range$|year must be in 1..9999",
+ ):
KmlDateTime.parse("0000")
def test_parse_year_month(self) -> None:
dt = KmlDateTime.parse("2000-03")
+ assert dt
assert dt.resolution == DateTimeResolution.year_month
assert dt.dt == datetime.datetime(2000, 3, 1, tzinfo=tzutc())
def test_parse_year_month_no_dash(self) -> None:
dt = KmlDateTime.parse("200004")
+ assert dt
assert dt.resolution == DateTimeResolution.year_month
assert dt.dt == datetime.datetime(2000, 4, 1, tzinfo=tzutc())
def test_parse_year_month_0(self) -> None:
- with pytest.raises(ValueError):
+ with pytest.raises(ValueError, match="month must be in 1..12"):
KmlDateTime.parse("2000-00")
def test_parse_year_month_13(self) -> None:
- with pytest.raises(ValueError):
+ with pytest.raises(ValueError, match="month must be in 1..12"):
KmlDateTime.parse("2000-13")
def test_parse_year_month_day(self) -> None:
dt = KmlDateTime.parse("2000-03-01")
+ assert dt
assert dt.resolution == DateTimeResolution.date
assert dt.dt == datetime.datetime(2000, 3, 1, tzinfo=tzutc())
def test_parse_year_month_day_no_dash(self) -> None:
dt = KmlDateTime.parse("20000401")
+ assert dt
assert dt.resolution == DateTimeResolution.date
assert dt.dt == datetime.datetime(2000, 4, 1, tzinfo=tzutc())
def test_parse_year_month_day_0(self) -> None:
- with pytest.raises(ValueError):
+ with pytest.raises(
+ ValueError,
+ match="^day is out of range for month$|day must be in 1..31",
+ ):
KmlDateTime.parse("2000-05-00")
def test_parse_datetime_utc(self) -> None:
dt = KmlDateTime.parse("1997-07-16T07:30:15Z")
+ assert dt
assert dt.resolution == DateTimeResolution.datetime
assert dt.dt == datetime.datetime(1997, 7, 16, 7, 30, 15, tzinfo=tzutc())
def test_parse_datetime_with_tz(self) -> None:
dt = KmlDateTime.parse("1997-07-16T07:30:15+01:00")
+ assert dt
assert dt.resolution == DateTimeResolution.datetime
assert dt.dt == datetime.datetime(
1997,
@@ -163,6 +179,7 @@ def test_parse_datetime_with_tz(self) -> None:
def test_parse_datetime_with_tz_no_colon(self) -> None:
dt = KmlDateTime.parse("1997-07-16T07:30:15+0100")
+ assert dt
assert dt.resolution == DateTimeResolution.datetime
assert dt.dt == datetime.datetime(
1997,
@@ -177,6 +194,7 @@ def test_parse_datetime_with_tz_no_colon(self) -> None:
def test_parse_datetime_no_tz(self) -> None:
dt = KmlDateTime.parse("1997-07-16T07:30:15")
+ assert dt
assert dt.resolution == DateTimeResolution.datetime
assert dt.dt == datetime.datetime(1997, 7, 16, 7, 30, 15, tzinfo=tzutc())
@@ -192,9 +210,12 @@ class TestStdLibrary(StdLibrary):
"""Test with the standard library."""
def test_timestamp(self) -> None:
- now = datetime.datetime.now()
+ now = datetime.datetime.now() # noqa: DTZ005
dt = KmlDateTime(now)
+
ts = kml.TimeStamp(timestamp=dt)
+
+ assert ts.timestamp
assert ts.timestamp.dt == now
assert ts.timestamp.resolution == DateTimeResolution.datetime
assert "TimeStamp>" in str(ts.to_string())
@@ -206,9 +227,11 @@ def test_timestamp(self) -> None:
assert "2000-01-01" in str(ts.to_string())
def test_timespan(self) -> None:
- now = KmlDateTime(datetime.datetime.now())
- y2k = KmlDateTime(datetime.datetime(2000, 1, 1))
+ now = KmlDateTime(datetime.datetime.now()) # noqa: DTZ005
+ y2k = KmlDateTime(datetime.datetime(2000, 1, 1)) # noqa: DTZ001
+
ts = kml.TimeSpan(end=now, begin=y2k)
+
assert ts.end == now
assert ts.begin == y2k
assert "TimeSpan>" in str(ts.to_string())
@@ -224,9 +247,12 @@ def test_timespan(self) -> None:
assert not ts
def test_feature_timestamp(self) -> None:
- now = datetime.datetime.now()
+ now = datetime.datetime.now() # noqa: DTZ005
f = kml.Document()
+
f.times = kml.TimeStamp(timestamp=KmlDateTime(now))
+
+ assert f.time_stamp
assert f.time_stamp.dt == now
assert now.isoformat() in str(f.to_string())
assert "TimeStamp>" in str(f.to_string())
@@ -238,8 +264,8 @@ def test_feature_timestamp(self) -> None:
assert "TimeStamp>" not in str(f.to_string())
def test_feature_timespan(self) -> None:
- now = datetime.datetime.now()
- y2k = datetime.datetime(2000, 1, 1)
+ now = datetime.datetime.now() # noqa: DTZ005
+ y2k = datetime.datetime(2000, 1, 1) # noqa: DTZ001
f = kml.Document()
f.times = kml.TimeSpan(begin=KmlDateTime(y2k), end=KmlDateTime(now))
assert f.begin == KmlDateTime(y2k)
@@ -265,8 +291,9 @@ def test_read_timestamp_year(self) -> None:
"""
- ts = kml.TimeStamp.class_from_string(doc, ns="")
+ ts = kml.TimeStamp.from_string(doc, ns="")
+ assert ts.timestamp
assert ts.timestamp.resolution == DateTimeResolution.year
assert ts.timestamp.dt == datetime.datetime(1997, 1, 1, 0, 0, tzinfo=tzutc())
@@ -277,8 +304,9 @@ def test_read_timestamp_year_month(self) -> None:
"""
- ts = kml.TimeStamp.class_from_string(doc, ns="")
+ ts = kml.TimeStamp.from_string(doc, ns="")
+ assert ts.timestamp
assert ts.timestamp.resolution == DateTimeResolution.year_month
assert ts.timestamp.dt == datetime.datetime(1997, 7, 1, 0, 0, tzinfo=tzutc())
@@ -289,8 +317,9 @@ def test_read_timestamp_ym_no_hyphen(self) -> None:
"""
- ts = kml.TimeStamp.class_from_string(doc, ns="")
+ ts = kml.TimeStamp.from_string(doc, ns="")
+ assert ts.timestamp
assert ts.timestamp.resolution == DateTimeResolution.year_month
assert ts.timestamp.dt == datetime.datetime(1998, 8, 1, 0, 0, tzinfo=tzutc())
@@ -301,8 +330,9 @@ def test_read_timestamp_ymd(self) -> None:
"""
- ts = kml.TimeStamp.class_from_string(doc, ns="")
+ ts = kml.TimeStamp.from_string(doc, ns="")
+ assert ts.timestamp
assert ts.timestamp.resolution == DateTimeResolution.date
assert ts.timestamp.dt == datetime.datetime(1997, 7, 16, 0, 0, tzinfo=tzutc())
@@ -316,8 +346,9 @@ def test_read_timestamp_utc(self) -> None:
"""
- ts = kml.TimeStamp.class_from_string(doc, ns="")
+ ts = kml.TimeStamp.from_string(doc, ns="")
+ assert ts.timestamp
assert ts.timestamp.resolution == DateTimeResolution.datetime
assert ts.timestamp.dt == datetime.datetime(
1997,
@@ -336,8 +367,9 @@ def test_read_timestamp_utc_offset(self) -> None:
"""
- ts = kml.TimeStamp.class_from_string(doc, ns="")
+ ts = kml.TimeStamp.from_string(doc, ns="")
+ assert ts.timestamp
assert ts.timestamp.resolution == DateTimeResolution.datetime
assert ts.timestamp.dt == datetime.datetime(
1997,
@@ -357,10 +389,12 @@ def test_read_timespan(self) -> None:
"""
- ts = kml.TimeSpan.class_from_string(doc, ns="")
+ ts = kml.TimeSpan.from_string(doc, ns="")
+ assert ts.begin
assert ts.begin.resolution == DateTimeResolution.date
assert ts.begin.dt == datetime.datetime(1876, 8, 1, 0, 0, tzinfo=tzutc())
+ assert ts.end
assert ts.end.resolution == DateTimeResolution.datetime
assert ts.end.dt == datetime.datetime(1997, 7, 16, 7, 30, 15, tzinfo=tzutc())
@@ -377,10 +411,12 @@ def test_feature_fromstring(self) -> None:
"""
- d = kml.Document.class_from_string(doc, ns="")
+ d = kml.Document.from_string(doc, ns="")
assert d.time_stamp is None
+ assert d.begin
assert d.begin.dt == datetime.datetime(1876, 8, 1, 0, 0, tzinfo=tzutc())
+ assert d.end
assert d.end.dt == datetime.datetime(1997, 7, 16, 7, 30, 15, tzinfo=tzutc())
diff --git a/tests/utils_test.py b/tests/utils_test.py
new file mode 100644
index 00000000..b7bb79a6
--- /dev/null
+++ b/tests/utils_test.py
@@ -0,0 +1,215 @@
+# Copyright (C) 2021 - 2023 Christian Ledermann
+#
+# This library is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This library is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+"""Test the utils module."""
+from typing import List
+
+from fastkml import Schema
+from fastkml import SchemaData
+from fastkml import kml
+from fastkml.utils import find
+from fastkml.utils import find_all
+from tests.base import Lxml
+from tests.base import StdLibrary
+
+
+class TestFindAll(StdLibrary):
+ """Test the find_all function."""
+
+ def test_find_all(self) -> None:
+
+ class A:
+ def __init__(self, x: int) -> None:
+ self.x = x
+
+ class B:
+ def __init__(self, y: int) -> None:
+ self.y = y
+
+ class C:
+ def __init__(self, z: int) -> None:
+ self.z = z
+
+ class D:
+ def __init__(self, a: A, b: B, c: C) -> None:
+ self.a = a
+ self.b = b
+ self.c = c
+
+ a1 = A(1)
+ a2 = A(2)
+ b1 = B(1)
+ b2 = B(2)
+ c1 = C(1)
+ c2 = C(2)
+ d1 = D(a1, b1, c1)
+ d2 = D(a2, b2, c2)
+
+ result = list(find_all(d1, of_type=A))
+ assert result == [a1]
+
+ result = list(find_all(d1, of_type=B))
+ assert result == [b1]
+
+ result = list(find_all(d1, of_type=C))
+ assert result == [c1]
+
+ result = list(find_all(d1, of_type=D))
+ assert result == [d1]
+
+ result = list(find_all(d1, of_type=A, x=1))
+ assert result == [a1]
+
+ result = list(find_all(d1, of_type=A, x=2))
+ assert not result
+
+ result = list(find_all(d2, of_type=A, x=2))
+ assert result == [a2]
+
+ def test_find_all_empty(self) -> None:
+ result = list(find_all(None, of_type=None))
+ assert result == [None]
+
+ def test_find_all_no_type(self) -> None:
+ class A:
+ def __init__(self, x: int) -> None:
+ self.x = x
+
+ a1 = A(1)
+
+ result = list(find_all(a1, of_type=None))
+ assert result == [a1, 1]
+
+ def test_find_all_no_type_attr(self) -> None:
+ class A:
+ def __init__(self, x: int, y: int) -> None:
+ self.x = x
+ self.y = y
+
+ class B:
+ def __init__(self, a: List[A]) -> None:
+ self.a = a
+
+ a1 = A(1, 0)
+ a2 = A(0, 1)
+ a3 = A(1, 1)
+ b = B([a1, a2, a3])
+
+ assert list(find_all(b, x=1)) == [a1, a3]
+ assert list(find_all(b, y=1)) == [a2, a3]
+ assert list(find_all(b, x=0)) == [a2]
+ assert list(find_all(b, x=1, y=1)) == [a3]
+
+ def test_find_no_type_attr(self) -> None:
+ class A:
+ def __init__(self, x: int, y: int) -> None:
+ self.x = x
+ self.y = y
+
+ class B:
+ def __init__(self, a: List[A]) -> None:
+ self.a = a
+
+ a1 = A(1, 0)
+ a2 = A(0, 1)
+ a3 = A(1, 1)
+ b = B([a1, a2, a3])
+
+ assert find(b, x=1) == a1
+ assert find(b, y=1) == a2
+ assert find(b, x=0) == a2
+ assert find(b, x=1, y=1) == a3
+
+ def test_find_schema_by_url(self) -> None:
+ doc = (
+ ''
+ ""
+ "ExtendedData+SchemaData "
+ "1 "
+ ""
+ '"
+ ''
+ ''
+ ''
+ "Trail Head Name]]> "
+ " "
+ ''
+ "The length in miles]]> "
+ " "
+ ''
+ "change in altitude]]> "
+ " "
+ " "
+ ""
+ ""
+ "Easy trail "
+ "#trailhead-balloon-template "
+ ""
+ ''
+ 'Pi in the sky '
+ '3.14159 '
+ '10 '
+ " "
+ " "
+ ""
+ "-122.000,37.002 "
+ " "
+ " "
+ ""
+ "Difficult trail "
+ "#trailhead-balloon-template "
+ ""
+ ''
+ 'Mount Everest '
+ '347.45 '
+ '10000 '
+ " "
+ " "
+ ""
+ "-121.998,37.0078 "
+ " "
+ " "
+ " "
+ " "
+ )
+ k = kml.KML.from_string(doc, strict=False)
+
+ schema = find(k, of_type=Schema, id="TrailHeadTypeId")
+ schema_data = list(find_all(k, of_type=SchemaData))
+
+ assert isinstance(schema, Schema)
+ assert schema.name == "TrailHeadType"
+ assert schema.id == "TrailHeadTypeId"
+ assert len(schema_data) == 2
+ for data in schema_data:
+ assert isinstance(data, SchemaData)
+ assert data.schema_url == "#TrailHeadTypeId"
+
+
+class TestFindAllLxml(Lxml):
+ """Run the tests using lxml."""
diff --git a/tests/validator_test.py b/tests/validator_test.py
new file mode 100644
index 00000000..13a5d99c
--- /dev/null
+++ b/tests/validator_test.py
@@ -0,0 +1,108 @@
+# Copyright (C) 2024 Christian Ledermann
+#
+# This library is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This library is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+"""Test the validator module."""
+from pathlib import Path
+from typing import Final
+
+import pytest
+
+from fastkml import atom
+from fastkml.validator import get_schema_parser
+from fastkml.validator import validate
+from tests.base import Lxml
+from tests.base import StdLibrary
+
+TEST_DIR: Final = Path(__file__).parent
+
+
+class TestStdLibrary(StdLibrary):
+
+ def setup_method(self) -> None:
+ """Invalidate the cache before each test."""
+ get_schema_parser.cache_clear()
+ super().setup_method()
+
+ def test_validate(self) -> None:
+ assert (
+ validate(
+ file_to_validate=TEST_DIR
+ / "ogc_conformance"
+ / "data"
+ / "kml"
+ / "Document-clean.kml",
+ )
+ is None
+ )
+
+ def test_validate_require_element_or_path(self) -> None:
+ with pytest.raises(
+ ValueError,
+ match="^Either element or file_to_validate must be provided.$",
+ ):
+ validate()
+
+ def test_validate_mutual_exclusive_element_and_path(self) -> None:
+ with pytest.raises(
+ ValueError,
+ match="^Only one of element and file_to_validate can be provided.$",
+ ):
+ validate(
+ element=atom.Link().etree_element(),
+ file_to_validate=TEST_DIR / "test.xml",
+ )
+
+
+class TestLxml(Lxml):
+
+ def setup_method(self) -> None:
+ """Invalidate the cache before each test."""
+ get_schema_parser.cache_clear()
+ super().setup_method()
+
+ def test_validate(self) -> None:
+ assert validate(
+ file_to_validate=TEST_DIR
+ / "ogc_conformance"
+ / "data"
+ / "kml"
+ / "Document-clean.kml",
+ )
+
+ def test_validate_element(self) -> None:
+ link = atom.Link(
+ ns="{http://www.w3.org/2005/Atom}",
+ href="#here",
+ rel="alternate",
+ type="text/html",
+ hreflang="en",
+ title="Title",
+ length=3456,
+ )
+ assert validate(element=link.etree_element())
+
+ def test_validate_invalid_element(self) -> None:
+ link = atom.Link(
+ ns="{http://www.w3.org/2005/Atom}",
+ href="",
+ rel="alternate",
+ type="text/html",
+ hreflang="en",
+ title="Title",
+ length=3456,
+ )
+
+ with pytest.raises(AssertionError):
+ validate(element=link.etree_element())
diff --git a/tests/views_test.py b/tests/views_test.py
index bd185b84..097dfaae 100644
--- a/tests/views_test.py
+++ b/tests/views_test.py
@@ -87,7 +87,7 @@ def test_camera_read(self) -> None:
""
)
- camera = views.Camera.class_from_string(camera_xml)
+ camera = views.Camera.from_string(camera_xml)
assert camera.heading == 10
assert camera.tilt == 20
@@ -156,7 +156,7 @@ def test_look_at_read(self) -> None:
"30 "
""
)
- look_at = views.LookAt.class_from_string(look_at_xml)
+ look_at = views.LookAt.from_string(look_at_xml)
assert look_at.heading == 10
assert look_at.tilt == 20
@@ -209,6 +209,7 @@ def test_region_with_all_optional_parameters(self) -> None:
assert region.lod.min_fade_extent == 0
assert region.lod.max_fade_extent == 512
assert region
+ assert bool(region)
def test_region_read(self) -> None:
doc = (
@@ -224,7 +225,7 @@ def test_region_read(self) -> None:
"512 "
)
- region = views.Region.class_from_string(doc)
+ region = views.Region.from_string(doc)
assert region.id == "region1"
assert region.lat_lon_alt_box.north == 37.85
[…] earlier. It’s the result of a conversion of a polygon shapefile of country boundaries (from Natural Earth, a fantastic, public domain, physical/cultural spatial data source) to a raster data […]
+ + +[…] Le mappe sono scaricate da https://www.naturalearthdata.com […]
+ + +[…] Le mappe sono scaricate da https://www.naturalearthdata.com […]
+ + +