| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | /* | 
| 2 |  |  |  |  |  |  | * Map geographic coordinates to time zone names | 
| 3 |  |  |  |  |  |  | * | 
| 4 |  |  |  |  |  |  | * Copyright (C) 2023 Andreas Vögele | 
| 5 |  |  |  |  |  |  | * | 
| 6 |  |  |  |  |  |  | * This module is free software; you can redistribute it and/or modify it | 
| 7 |  |  |  |  |  |  | * under the same terms as Perl itself. | 
| 8 |  |  |  |  |  |  | */ | 
| 9 |  |  |  |  |  |  |  | 
| 10 |  |  |  |  |  |  | /* SPDX-License-Identifier: Artistic-1.0-Perl OR GPL-1.0-or-later */ | 
| 11 |  |  |  |  |  |  |  | 
| 12 |  |  |  |  |  |  | #define PERL_NO_GET_CONTEXT | 
| 13 |  |  |  |  |  |  | #include "EXTERN.h" | 
| 14 |  |  |  |  |  |  | #include "perl.h" | 
| 15 |  |  |  |  |  |  | #define NO_XSLOCKS | 
| 16 |  |  |  |  |  |  | #include "XSUB.h" | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | #ifdef MULTIPLICITY | 
| 19 |  |  |  |  |  |  | #define storeTHX(var) (var) = aTHX | 
| 20 |  |  |  |  |  |  | #define dTHXfield(var) tTHX var; | 
| 21 |  |  |  |  |  |  | #else | 
| 22 |  |  |  |  |  |  | #define storeTHX(var) dNOOP | 
| 23 |  |  |  |  |  |  | #define dTHXfield(var) | 
| 24 |  |  |  |  |  |  | #endif | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  | #include "shapereader/shapereader.h" | 
| 27 |  |  |  |  |  |  | #include | 
| 28 |  |  |  |  |  |  | #include | 
| 29 |  |  |  |  |  |  | #include | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | /* Index the shapes by their bounding box. */ | 
| 32 |  |  |  |  |  |  | struct index_entry { | 
| 33 |  |  |  |  |  |  | shp_box_t box;      /* Bounding box from the shp file */ | 
| 34 |  |  |  |  |  |  | size_t file_offset; /* File position of the corresponding polygon */ | 
| 35 |  |  |  |  |  |  | SV *time_zone;      /* Time zone name from the dbf file */ | 
| 36 |  |  |  |  |  |  | }; | 
| 37 |  |  |  |  |  |  |  | 
| 38 |  |  |  |  |  |  | struct index { | 
| 39 |  |  |  |  |  |  | size_t num_entries; | 
| 40 |  |  |  |  |  |  | struct index_entry *entries; | 
| 41 |  |  |  |  |  |  | struct index_entry **matches; /* Entries that match a location */ | 
| 42 |  |  |  |  |  |  | }; | 
| 43 |  |  |  |  |  |  |  | 
| 44 |  |  |  |  |  |  | typedef struct geo_location_timezonefinder { | 
| 45 |  |  |  |  |  |  | SV *dbf_filename; | 
| 46 |  |  |  |  |  |  | SV *shp_filename; | 
| 47 |  |  |  |  |  |  | FILE *dbf_fp; | 
| 48 |  |  |  |  |  |  | FILE *shp_fp; | 
| 49 |  |  |  |  |  |  | size_t dbf_num; | 
| 50 |  |  |  |  |  |  | size_t shp_num; | 
| 51 |  |  |  |  |  |  | struct index index; | 
| 52 |  |  |  |  |  |  | dbf_file_t dbf_fh; | 
| 53 |  |  |  |  |  |  | shp_file_t shp_fh; | 
| 54 |  |  |  |  |  |  | dTHXfield(perl) | 
| 55 |  |  |  |  |  |  | } *Geo__Location__TimeZoneFinder; | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  | static void | 
| 58 | 1 |  |  |  |  |  | init_index(Geo__Location__TimeZoneFinder self, size_t num_entries) | 
| 59 |  |  |  |  |  |  | { | 
| 60 |  |  |  |  |  |  | dTHXa(self->perl); | 
| 61 |  |  |  |  |  |  | struct index *index; | 
| 62 |  |  |  |  |  |  |  | 
| 63 | 1 |  |  |  |  |  | index = &self->index; | 
| 64 | 1 | 50 |  |  |  |  | Newxz(index->entries, num_entries, struct index_entry); | 
| 65 | 1 | 50 |  |  |  |  | Newxz(index->matches, num_entries, struct index_entry *); | 
| 66 | 1 |  |  |  |  |  | index->num_entries = num_entries; | 
| 67 | 1 |  |  |  |  |  | } | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | static void | 
| 70 | 2 |  |  |  |  |  | free_index(Geo__Location__TimeZoneFinder self) | 
| 71 |  |  |  |  |  |  | { | 
| 72 |  |  |  |  |  |  | dTHXa(self->perl); | 
| 73 |  |  |  |  |  |  | struct index *index; | 
| 74 |  |  |  |  |  |  | struct index_entry *entry; | 
| 75 |  |  |  |  |  |  | size_t n, i; | 
| 76 |  |  |  |  |  |  |  | 
| 77 | 2 |  |  |  |  |  | index = &self->index; | 
| 78 | 2 | 100 |  |  |  |  | if (index->entries != NULL) { | 
| 79 | 1 |  |  |  |  |  | n = index->num_entries; | 
| 80 | 7 | 100 |  |  |  |  | for (i = 0; i < n; ++i) { | 
| 81 | 6 |  |  |  |  |  | entry = &index->entries[i]; | 
| 82 | 6 |  |  |  |  |  | SvREFCNT_dec(entry->time_zone); | 
| 83 |  |  |  |  |  |  | } | 
| 84 | 1 |  |  |  |  |  | Safefree(index->entries); | 
| 85 | 1 |  |  |  |  |  | index->entries = NULL; | 
| 86 |  |  |  |  |  |  | } | 
| 87 | 2 | 100 |  |  |  |  | if (index->matches != NULL) { | 
| 88 | 1 |  |  |  |  |  | Safefree(index->matches); | 
| 89 | 1 |  |  |  |  |  | index->matches = NULL; | 
| 90 |  |  |  |  |  |  | } | 
| 91 | 2 |  |  |  |  |  | } | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | static void | 
| 94 | 2 |  |  |  |  |  | free_self(Geo__Location__TimeZoneFinder self) | 
| 95 |  |  |  |  |  |  | { | 
| 96 |  |  |  |  |  |  | dTHXa(self->perl); | 
| 97 |  |  |  |  |  |  |  | 
| 98 | 2 |  |  |  |  |  | free_index(self); | 
| 99 | 2 | 50 |  |  |  |  | if (self->dbf_fp != NULL) { | 
| 100 | 0 |  |  |  |  |  | (void) fclose(self->dbf_fp); | 
| 101 | 0 |  |  |  |  |  | self->dbf_fp = NULL; | 
| 102 |  |  |  |  |  |  | } | 
| 103 | 2 | 100 |  |  |  |  | if (self->shp_fp != NULL) { | 
| 104 | 1 |  |  |  |  |  | (void) fclose(self->shp_fp); | 
| 105 | 1 |  |  |  |  |  | self->shp_fp = NULL; | 
| 106 |  |  |  |  |  |  | } | 
| 107 | 2 | 50 |  |  |  |  | if (self->dbf_filename != NULL) { | 
| 108 | 2 |  |  |  |  |  | SvREFCNT_dec(self->dbf_filename); | 
| 109 |  |  |  |  |  |  | } | 
| 110 | 2 | 50 |  |  |  |  | if (self->shp_filename != NULL) { | 
| 111 | 2 |  |  |  |  |  | SvREFCNT_dec(self->shp_filename); | 
| 112 |  |  |  |  |  |  | } | 
| 113 | 2 |  |  |  |  |  | Safefree(self); | 
| 114 | 2 |  |  |  |  |  | } | 
| 115 |  |  |  |  |  |  |  | 
| 116 |  |  |  |  |  |  | static int | 
| 117 | 7 |  |  |  |  |  | is_tzid(const dbf_field_t *field) | 
| 118 |  |  |  |  |  |  | { | 
| 119 | 7 |  |  |  |  |  | return field->type == DBFT_CHARACTER; | 
| 120 |  |  |  |  |  |  | } | 
| 121 |  |  |  |  |  |  |  | 
| 122 |  |  |  |  |  |  | static int | 
| 123 | 1 |  |  |  |  |  | handle_dbf_header(dbf_file_t *fh, const dbf_header_t *header) | 
| 124 |  |  |  |  |  |  | { | 
| 125 | 1 |  |  |  |  |  | Geo__Location__TimeZoneFinder self = | 
| 126 |  |  |  |  |  |  | (Geo__Location__TimeZoneFinder) fh->user_data; | 
| 127 |  |  |  |  |  |  | const dbf_field_t *field; | 
| 128 |  |  |  |  |  |  | int has_tzid; | 
| 129 |  |  |  |  |  |  |  | 
| 130 | 1 |  |  |  |  |  | self->dbf_num = 0; | 
| 131 |  |  |  |  |  |  |  | 
| 132 | 1 |  |  |  |  |  | has_tzid = 0; | 
| 133 |  |  |  |  |  |  |  | 
| 134 | 1 |  |  |  |  |  | field = header->fields; | 
| 135 | 1 | 50 |  |  |  |  | while (field != NULL) { | 
| 136 | 1 | 50 |  |  |  |  | if (is_tzid(field)) { | 
| 137 | 1 |  |  |  |  |  | has_tzid = 1; | 
| 138 | 1 |  |  |  |  |  | break; | 
| 139 |  |  |  |  |  |  | } | 
| 140 | 0 |  |  |  |  |  | field = field->next; | 
| 141 |  |  |  |  |  |  | } | 
| 142 |  |  |  |  |  |  |  | 
| 143 | 1 | 50 |  |  |  |  | if (!has_tzid) { | 
| 144 | 0 |  |  |  |  |  | dbf_error(fh, "No tzid field"); | 
| 145 | 0 |  |  |  |  |  | return -1; | 
| 146 |  |  |  |  |  |  | } | 
| 147 |  |  |  |  |  |  |  | 
| 148 | 1 | 50 |  |  |  |  | if (header->num_records == 0) { | 
| 149 | 0 |  |  |  |  |  | dbf_error(fh, "No records"); | 
| 150 | 0 |  |  |  |  |  | return -1; | 
| 151 |  |  |  |  |  |  | } | 
| 152 |  |  |  |  |  |  |  | 
| 153 | 1 |  |  |  |  |  | init_index(self, header->num_records); | 
| 154 |  |  |  |  |  |  |  | 
| 155 | 1 |  |  |  |  |  | return 1; | 
| 156 |  |  |  |  |  |  | } | 
| 157 |  |  |  |  |  |  |  | 
| 158 |  |  |  |  |  |  | static int | 
| 159 | 6 |  |  |  |  |  | handle_dbf_record(dbf_file_t *fh, const dbf_header_t *header, | 
| 160 |  |  |  |  |  |  | const dbf_record_t *record, size_t file_offset) | 
| 161 |  |  |  |  |  |  | { | 
| 162 | 6 |  |  |  |  |  | Geo__Location__TimeZoneFinder self = | 
| 163 |  |  |  |  |  |  | (Geo__Location__TimeZoneFinder) fh->user_data; | 
| 164 |  |  |  |  |  |  | dTHXa(self->perl); | 
| 165 |  |  |  |  |  |  | struct index *index; | 
| 166 |  |  |  |  |  |  | struct index_entry *entry; | 
| 167 |  |  |  |  |  |  | const dbf_field_t *field; | 
| 168 |  |  |  |  |  |  | const char *s; | 
| 169 |  |  |  |  |  |  | size_t len; | 
| 170 |  |  |  |  |  |  |  | 
| 171 | 6 |  |  |  |  |  | index = &self->index; | 
| 172 |  |  |  |  |  |  |  | 
| 173 | 6 | 50 |  |  |  |  | if (dbf_record_is_deleted(record)) { | 
| 174 | 0 |  |  |  |  |  | return 1; | 
| 175 |  |  |  |  |  |  | } | 
| 176 |  |  |  |  |  |  |  | 
| 177 | 6 | 50 |  |  |  |  | if (self->dbf_num >= index->num_entries) { | 
| 178 | 0 |  |  |  |  |  | dbf_error(fh, "Expected %zu records, got %zu", index->num_entries, | 
| 179 |  |  |  |  |  |  | self->dbf_num); | 
| 180 | 0 |  |  |  |  |  | return -1; | 
| 181 |  |  |  |  |  |  | } | 
| 182 |  |  |  |  |  |  |  | 
| 183 | 6 |  |  |  |  |  | entry = &index->entries[self->dbf_num]; | 
| 184 | 6 |  |  |  |  |  | field = header->fields; | 
| 185 | 6 | 50 |  |  |  |  | while (field != NULL) { | 
| 186 | 6 | 50 |  |  |  |  | if (is_tzid(field)) { | 
| 187 |  |  |  |  |  |  | /* Time zone names are plain ASCII. */ | 
| 188 | 6 |  |  |  |  |  | dbf_record_string(record, field, &s, &len); | 
| 189 | 6 |  |  |  |  |  | entry->time_zone = newSVpv(s, len); | 
| 190 | 6 |  |  |  |  |  | ++self->dbf_num; | 
| 191 | 6 |  |  |  |  |  | break; | 
| 192 |  |  |  |  |  |  | } | 
| 193 | 0 |  |  |  |  |  | field = field->next; | 
| 194 |  |  |  |  |  |  | } | 
| 195 |  |  |  |  |  |  |  | 
| 196 | 6 |  |  |  |  |  | return 1; | 
| 197 |  |  |  |  |  |  | } | 
| 198 |  |  |  |  |  |  |  | 
| 199 |  |  |  |  |  |  | static int | 
| 200 | 1 |  |  |  |  |  | handle_shp_header(shp_file_t *fh, const shp_header_t *header) | 
| 201 |  |  |  |  |  |  | { | 
| 202 | 1 |  |  |  |  |  | Geo__Location__TimeZoneFinder self = | 
| 203 |  |  |  |  |  |  | (Geo__Location__TimeZoneFinder) fh->user_data; | 
| 204 |  |  |  |  |  |  |  | 
| 205 | 1 |  |  |  |  |  | self->shp_num = 0; | 
| 206 |  |  |  |  |  |  |  | 
| 207 | 1 |  |  |  |  |  | return 1; | 
| 208 |  |  |  |  |  |  | } | 
| 209 |  |  |  |  |  |  |  | 
| 210 |  |  |  |  |  |  | static int | 
| 211 | 6 |  |  |  |  |  | handle_shp_record(shp_file_t *fh, const shp_header_t *header, | 
| 212 |  |  |  |  |  |  | const shp_record_t *record, size_t file_offset) | 
| 213 |  |  |  |  |  |  | { | 
| 214 | 6 |  |  |  |  |  | Geo__Location__TimeZoneFinder self = | 
| 215 |  |  |  |  |  |  | (Geo__Location__TimeZoneFinder) fh->user_data; | 
| 216 |  |  |  |  |  |  | struct index *index; | 
| 217 |  |  |  |  |  |  | struct index_entry *entry; | 
| 218 |  |  |  |  |  |  |  | 
| 219 | 6 |  |  |  |  |  | index = &self->index; | 
| 220 |  |  |  |  |  |  |  | 
| 221 | 6 | 50 |  |  |  |  | if (self->shp_num >= index->num_entries) { | 
| 222 | 0 |  |  |  |  |  | shp_error(fh, "Expected %zu records, got %zu", index->num_entries, | 
| 223 |  |  |  |  |  |  | self->shp_num); | 
| 224 | 0 |  |  |  |  |  | return -1; | 
| 225 |  |  |  |  |  |  | } | 
| 226 |  |  |  |  |  |  |  | 
| 227 | 6 |  |  |  |  |  | entry = &index->entries[self->shp_num]; | 
| 228 | 6 | 50 |  |  |  |  | if (record->shape_type == SHPT_POLYGON) { | 
| 229 | 6 |  |  |  |  |  | entry->box = record->shape.polygon.box; | 
| 230 | 6 |  |  |  |  |  | entry->file_offset = file_offset; | 
| 231 | 6 |  |  |  |  |  | ++self->shp_num; | 
| 232 |  |  |  |  |  |  | } | 
| 233 |  |  |  |  |  |  |  | 
| 234 | 6 |  |  |  |  |  | return 1; | 
| 235 |  |  |  |  |  |  | } | 
| 236 |  |  |  |  |  |  |  | 
| 237 |  |  |  |  |  |  | struct ocean_zone { | 
| 238 |  |  |  |  |  |  | const char *tzid; | 
| 239 |  |  |  |  |  |  | double left; | 
| 240 |  |  |  |  |  |  | double right; | 
| 241 |  |  |  |  |  |  | }; | 
| 242 |  |  |  |  |  |  |  | 
| 243 |  |  |  |  |  |  | static const struct ocean_zone ocean_zones[25] = { | 
| 244 |  |  |  |  |  |  | {"Etc/GMT-12", 172.5, 180.0}, | 
| 245 |  |  |  |  |  |  | {"Etc/GMT-11", 157.5, 172.5}, | 
| 246 |  |  |  |  |  |  | {"Etc/GMT-10", 142.5, 157.5}, | 
| 247 |  |  |  |  |  |  | {"Etc/GMT-9", 127.5, 142.5}, | 
| 248 |  |  |  |  |  |  | {"Etc/GMT-8", 112.5, 127.5}, | 
| 249 |  |  |  |  |  |  | {"Etc/GMT-7", 97.5, 112.5}, | 
| 250 |  |  |  |  |  |  | {"Etc/GMT-6", 82.5, 97.5}, | 
| 251 |  |  |  |  |  |  | {"Etc/GMT-5", 67.5, 82.5}, | 
| 252 |  |  |  |  |  |  | {"Etc/GMT-4", 52.5, 67.5}, | 
| 253 |  |  |  |  |  |  | {"Etc/GMT-3", 37.5, 52.5}, | 
| 254 |  |  |  |  |  |  | {"Etc/GMT-2", 22.5, 37.5}, | 
| 255 |  |  |  |  |  |  | {"Etc/GMT-1", 7.5, 22.5}, | 
| 256 |  |  |  |  |  |  | {"Etc/GMT", -7.5, 7.5}, | 
| 257 |  |  |  |  |  |  | {"Etc/GMT+1", -22.5, -7.5}, | 
| 258 |  |  |  |  |  |  | {"Etc/GMT+2", -37.5, -22.5}, | 
| 259 |  |  |  |  |  |  | {"Etc/GMT+3", -52.5, -37.5}, | 
| 260 |  |  |  |  |  |  | {"Etc/GMT+4", -67.5, -52.5}, | 
| 261 |  |  |  |  |  |  | {"Etc/GMT+5", -82.5, -67.5}, | 
| 262 |  |  |  |  |  |  | {"Etc/GMT+6", -97.5, -82.5}, | 
| 263 |  |  |  |  |  |  | {"Etc/GMT+7", -112.5, -97.5}, | 
| 264 |  |  |  |  |  |  | {"Etc/GMT+8", -127.5, -112.5}, | 
| 265 |  |  |  |  |  |  | {"Etc/GMT+9", -142.5, -127.5}, | 
| 266 |  |  |  |  |  |  | {"Etc/GMT+10", -157.5, -142.5}, | 
| 267 |  |  |  |  |  |  | {"Etc/GMT+11", -172.5, -157.5}, | 
| 268 |  |  |  |  |  |  | {"Etc/GMT+12", -180.0, -172.5} | 
| 269 |  |  |  |  |  |  | }; | 
| 270 |  |  |  |  |  |  |  | 
| 271 |  |  |  |  |  |  | static void | 
| 272 | 10 |  |  |  |  |  | get_special_time_zones(Geo__Location__TimeZoneFinder self, | 
| 273 |  |  |  |  |  |  | const shp_point_t *location, AV *time_zones) | 
| 274 |  |  |  |  |  |  | { | 
| 275 |  |  |  |  |  |  | dTHXa(self->perl); | 
| 276 |  |  |  |  |  |  | SV *time_zone; | 
| 277 |  |  |  |  |  |  | double lat, lon; | 
| 278 |  |  |  |  |  |  | int i; | 
| 279 |  |  |  |  |  |  |  | 
| 280 | 10 |  |  |  |  |  | lon = location->x; | 
| 281 | 10 |  |  |  |  |  | lat = location->y; | 
| 282 | 10 | 100 |  |  |  |  | if (lat == 90.0) { | 
| 283 |  |  |  |  |  |  | /* North Pole */ | 
| 284 | 26 | 100 |  |  |  |  | for (i = 0; i < 25; ++i) { | 
| 285 | 25 |  |  |  |  |  | time_zone = newSVpv(ocean_zones[i].tzid, 0); | 
| 286 | 25 |  |  |  |  |  | av_push(time_zones, time_zone); | 
| 287 |  |  |  |  |  |  | } | 
| 288 |  |  |  |  |  |  | } | 
| 289 | 9 | 100 |  |  |  |  | else if (lon == -180.0 || lon == 180.0) { | 
|  |  | 100 |  |  |  |  |  | 
| 290 |  |  |  |  |  |  | /* International Date Line */ | 
| 291 | 2 |  |  |  |  |  | time_zone = newSVpv(ocean_zones[0].tzid, 0); | 
| 292 | 2 |  |  |  |  |  | av_push(time_zones, time_zone); | 
| 293 | 2 |  |  |  |  |  | time_zone = newSVpv(ocean_zones[24].tzid, 0); | 
| 294 | 2 |  |  |  |  |  | av_push(time_zones, time_zone); | 
| 295 |  |  |  |  |  |  | } | 
| 296 | 10 |  |  |  |  |  | } | 
| 297 |  |  |  |  |  |  |  | 
| 298 |  |  |  |  |  |  | static void | 
| 299 | 4 |  |  |  |  |  | get_time_zones_at_sea(Geo__Location__TimeZoneFinder self, | 
| 300 |  |  |  |  |  |  | const shp_point_t *location, AV *time_zones) | 
| 301 |  |  |  |  |  |  | { | 
| 302 |  |  |  |  |  |  | dTHXa(self->perl); | 
| 303 |  |  |  |  |  |  | SV *time_zone; | 
| 304 |  |  |  |  |  |  | const struct ocean_zone *z; | 
| 305 |  |  |  |  |  |  | double lon; | 
| 306 |  |  |  |  |  |  | int i; | 
| 307 |  |  |  |  |  |  |  | 
| 308 | 4 |  |  |  |  |  | lon = location->x; | 
| 309 | 66 | 100 |  |  |  |  | for (i = 0; i < 25; ++i) { | 
| 310 | 65 |  |  |  |  |  | z = &ocean_zones[i]; | 
| 311 | 65 | 100 |  |  |  |  | if (lon >= z->left && lon <= z->right) { | 
|  |  | 100 |  |  |  |  |  | 
| 312 | 5 |  |  |  |  |  | time_zone = newSVpv(z->tzid, 0); | 
| 313 | 5 |  |  |  |  |  | av_push(time_zones, time_zone); | 
| 314 |  |  |  |  |  |  | } | 
| 315 | 65 | 100 |  |  |  |  | if (lon >= z->right) { | 
| 316 | 3 |  |  |  |  |  | break; | 
| 317 |  |  |  |  |  |  | } | 
| 318 |  |  |  |  |  |  | } | 
| 319 | 4 |  |  |  |  |  | } | 
| 320 |  |  |  |  |  |  |  | 
| 321 |  |  |  |  |  |  | static void | 
| 322 | 7 |  |  |  |  |  | get_time_zones(Geo__Location__TimeZoneFinder self, | 
| 323 |  |  |  |  |  |  | const shp_point_t *location, AV *time_zones) | 
| 324 |  |  |  |  |  |  | { | 
| 325 |  |  |  |  |  |  | dTHXa(self->perl); | 
| 326 |  |  |  |  |  |  | struct index *index; | 
| 327 |  |  |  |  |  |  | struct index_entry *entry; | 
| 328 |  |  |  |  |  |  | size_t n, m, i; | 
| 329 |  |  |  |  |  |  | size_t offset; | 
| 330 |  |  |  |  |  |  | FILE *fp; | 
| 331 |  |  |  |  |  |  | shp_file_t *fh; | 
| 332 |  |  |  |  |  |  | shp_record_t *record; | 
| 333 |  |  |  |  |  |  | shp_polygon_t *polygon; | 
| 334 |  |  |  |  |  |  |  | 
| 335 | 7 |  |  |  |  |  | index = &self->index; | 
| 336 |  |  |  |  |  |  |  | 
| 337 |  |  |  |  |  |  | /* How many bounding boxes contain the location? */ | 
| 338 | 7 |  |  |  |  |  | m = 0; | 
| 339 | 7 |  |  |  |  |  | n = index->num_entries; | 
| 340 | 49 | 100 |  |  |  |  | for (i = 0; i < n; ++i) { | 
| 341 | 42 |  |  |  |  |  | entry = &index->entries[i]; | 
| 342 | 42 | 100 |  |  |  |  | if (shp_box_point_in_box(&entry->box, location) != 0) { | 
| 343 | 6 |  |  |  |  |  | index->matches[m] = entry; | 
| 344 | 6 |  |  |  |  |  | ++m; | 
| 345 |  |  |  |  |  |  | } | 
| 346 |  |  |  |  |  |  | } | 
| 347 |  |  |  |  |  |  |  | 
| 348 |  |  |  |  |  |  | /* If there is only one match, return immediately. */ | 
| 349 | 7 | 100 |  |  |  |  | if (m == 1) { | 
| 350 | 2 |  |  |  |  |  | entry = index->matches[0]; | 
| 351 | 2 |  |  |  |  |  | av_push(time_zones, SvREFCNT_inc(entry->time_zone)); | 
| 352 | 2 |  |  |  |  |  | return; | 
| 353 |  |  |  |  |  |  | } | 
| 354 |  |  |  |  |  |  |  | 
| 355 |  |  |  |  |  |  | /* Otherwise, check the polygons in the shp file. */ | 
| 356 | 5 |  |  |  |  |  | fh = &self->shp_fh; | 
| 357 | 5 |  |  |  |  |  | fp = fh->fp; | 
| 358 | 9 | 100 |  |  |  |  | for (i = 0; i < m; ++i) { | 
| 359 | 4 |  |  |  |  |  | entry = index->matches[i]; | 
| 360 | 4 |  |  |  |  |  | offset = entry->file_offset; | 
| 361 |  |  |  |  |  |  |  | 
| 362 | 4 | 50 |  |  |  |  | if (offset > (size_t) LONG_MAX | 
| 363 | 4 | 50 |  |  |  |  | || fseek(fp, (long) offset, SEEK_SET) != 0) { | 
| 364 | 0 |  |  |  |  |  | croak("Cannot set file position to %zu in \"%" SVf "\"", | 
| 365 | 0 |  |  |  |  |  | offset, SVfARG(self->shp_filename)); | 
| 366 |  |  |  |  |  |  | } | 
| 367 |  |  |  |  |  |  |  | 
| 368 | 4 |  |  |  |  |  | record = NULL; | 
| 369 |  |  |  |  |  |  |  | 
| 370 | 4 | 50 |  |  |  |  | if (shp_read_record(fh, &record) < 0) { | 
| 371 | 0 |  |  |  |  |  | croak("Error reading \"%" SVf "\": %s", | 
| 372 | 0 |  |  |  |  |  | SVfARG(self->shp_filename), fh->error); | 
| 373 |  |  |  |  |  |  | } | 
| 374 |  |  |  |  |  |  |  | 
| 375 | 4 | 50 |  |  |  |  | if (record != NULL) { | 
| 376 | 4 | 50 |  |  |  |  | if (record->shape_type == SHPT_POLYGON) { | 
| 377 | 4 |  |  |  |  |  | polygon = &record->shape.polygon; | 
| 378 | 4 | 100 |  |  |  |  | if (shp_polygon_point_in_polygon(polygon, location) != 0) { | 
| 379 | 2 |  |  |  |  |  | av_push(time_zones, SvREFCNT_inc(entry->time_zone)); | 
| 380 |  |  |  |  |  |  | } | 
| 381 |  |  |  |  |  |  | } | 
| 382 | 4 |  |  |  |  |  | free(record); | 
| 383 |  |  |  |  |  |  | } | 
| 384 |  |  |  |  |  |  | } | 
| 385 |  |  |  |  |  |  | } | 
| 386 |  |  |  |  |  |  |  | 
| 387 |  |  |  |  |  |  | MODULE = Geo::Location::TimeZoneFinder PACKAGE = Geo::Location::TimeZoneFinder | 
| 388 |  |  |  |  |  |  |  | 
| 389 |  |  |  |  |  |  | PROTOTYPES: DISABLE | 
| 390 |  |  |  |  |  |  |  | 
| 391 |  |  |  |  |  |  | TYPEMAP: < | 
| 392 |  |  |  |  |  |  | Geo::Location::TimeZoneFinder T_PTROBJ | 
| 393 |  |  |  |  |  |  | HERE | 
| 394 |  |  |  |  |  |  |  | 
| 395 |  |  |  |  |  |  | SV * | 
| 396 |  |  |  |  |  |  | new(klass, ...) | 
| 397 |  |  |  |  |  |  | SV *klass | 
| 398 |  |  |  |  |  |  | INIT: | 
| 399 |  |  |  |  |  |  | Geo__Location__TimeZoneFinder self; | 
| 400 | 3 |  |  |  |  |  | SV *file_base = NULL; | 
| 401 |  |  |  |  |  |  | I32 i; | 
| 402 |  |  |  |  |  |  | const char *key; | 
| 403 |  |  |  |  |  |  | SV *value; | 
| 404 |  |  |  |  |  |  | dbf_file_t *dbf_fh; | 
| 405 |  |  |  |  |  |  | shp_file_t *shp_fh; | 
| 406 |  |  |  |  |  |  | size_t expected_size; | 
| 407 |  |  |  |  |  |  | CODE: | 
| 408 | 3 |  |  |  |  |  | dXCPT; | 
| 409 |  |  |  |  |  |  |  | 
| 410 | 3 | 50 |  |  |  |  | if ((items - 1) % 2 != 0) { | 
| 411 | 0 |  |  |  |  |  | warn("Odd-length list passed to %" SVf " constructor", SVfARG(klass)); | 
| 412 |  |  |  |  |  |  | } | 
| 413 |  |  |  |  |  |  |  | 
| 414 | 5 | 100 |  |  |  |  | for (i = 1; i < items; i += 2) { | 
| 415 | 2 | 50 |  |  |  |  | key = SvPV_nolen_const(ST(i)); | 
| 416 | 2 |  |  |  |  |  | value = ST(i + 1); | 
| 417 | 2 | 50 |  |  |  |  | if (strEQ(key, "file_base")) { | 
| 418 | 2 |  |  |  |  |  | file_base = value; | 
| 419 |  |  |  |  |  |  | } | 
| 420 |  |  |  |  |  |  | } | 
| 421 |  |  |  |  |  |  |  | 
| 422 | 3 | 100 |  |  |  |  | if (file_base == NULL) { | 
| 423 | 1 |  |  |  |  |  | croak("The \"file_base\" parameter is mandatory"); | 
| 424 |  |  |  |  |  |  | } | 
| 425 |  |  |  |  |  |  |  | 
| 426 | 2 |  |  |  |  |  | Newxz(self, 1, struct geo_location_timezonefinder); | 
| 427 |  |  |  |  |  |  | storeTHX(self->perl); | 
| 428 |  |  |  |  |  |  |  | 
| 429 | 3 | 100 |  |  |  |  | XCPT_TRY_START { | 
| 430 | 2 |  |  |  |  |  | self->dbf_filename = newSVsv(file_base); | 
| 431 | 2 |  |  |  |  |  | sv_catpvs(self->dbf_filename, ".dbf"); | 
| 432 | 2 |  |  |  |  |  | self->shp_filename = newSVsv(file_base); | 
| 433 | 2 |  |  |  |  |  | sv_catpvs(self->shp_filename, ".shp"); | 
| 434 |  |  |  |  |  |  |  | 
| 435 |  |  |  |  |  |  | /* Open the database file with the time zones. */ | 
| 436 | 2 | 50 |  |  |  |  | self->dbf_fp = fopen(SvPV_nolen_const(self->dbf_filename), "rb"); | 
| 437 | 2 | 100 |  |  |  |  | if (self->dbf_fp == NULL) { | 
| 438 | 1 |  |  |  |  |  | croak("Error opening \"%" SVf "\"", SVfARG(self->dbf_filename)); | 
| 439 |  |  |  |  |  |  | } | 
| 440 |  |  |  |  |  |  |  | 
| 441 |  |  |  |  |  |  | /* Open the main file with the shapes. */ | 
| 442 | 1 | 50 |  |  |  |  | self->shp_fp = fopen(SvPV_nolen_const(self->shp_filename), "rb"); | 
| 443 | 1 | 50 |  |  |  |  | if (self->shp_fp == NULL) { | 
| 444 | 0 |  |  |  |  |  | croak("Error opening \"%" SVf "\"", SVfARG(self->shp_filename)); | 
| 445 |  |  |  |  |  |  | } | 
| 446 |  |  |  |  |  |  |  | 
| 447 |  |  |  |  |  |  | /* Read the time zones. */ | 
| 448 | 1 |  |  |  |  |  | dbf_fh = dbf_file(&self->dbf_fh, self->dbf_fp, self); | 
| 449 | 1 | 50 |  |  |  |  | if (dbf_read(dbf_fh, handle_dbf_header, handle_dbf_record) < 0) { | 
| 450 | 0 |  |  |  |  |  | croak("Error reading \"%" SVf "\": %s", | 
| 451 | 0 |  |  |  |  |  | SVfARG(self->dbf_filename), dbf_fh->error); | 
| 452 |  |  |  |  |  |  | } | 
| 453 |  |  |  |  |  |  |  | 
| 454 |  |  |  |  |  |  | /* The time zone file is no longer needed. */ | 
| 455 | 1 |  |  |  |  |  | (void) fclose(self->dbf_fp); | 
| 456 | 1 |  |  |  |  |  | self->dbf_fp = NULL; | 
| 457 |  |  |  |  |  |  |  | 
| 458 | 1 |  |  |  |  |  | expected_size = self->index.num_entries; | 
| 459 |  |  |  |  |  |  |  | 
| 460 | 1 | 50 |  |  |  |  | if (self->dbf_num != expected_size) { | 
| 461 | 0 |  |  |  |  |  | croak("Expected %zu records, got %zu in \"%" SVf "\"", | 
| 462 | 0 |  |  |  |  |  | expected_size, self->dbf_num, SVfARG(self->dbf_filename)); | 
| 463 |  |  |  |  |  |  | } | 
| 464 |  |  |  |  |  |  |  | 
| 465 |  |  |  |  |  |  | /* Index the shapes by their bounding boxes. */ | 
| 466 | 1 |  |  |  |  |  | shp_fh = shp_file(&self->shp_fh, self->shp_fp, self); | 
| 467 | 1 | 50 |  |  |  |  | if (shp_read(shp_fh, handle_shp_header, handle_shp_record) < 0) { | 
| 468 | 0 |  |  |  |  |  | croak("Error reading \"%" SVf "\": %s", | 
| 469 | 0 |  |  |  |  |  | SVfARG(self->shp_filename), shp_fh->error); | 
| 470 |  |  |  |  |  |  | } | 
| 471 |  |  |  |  |  |  |  | 
| 472 | 1 | 50 |  |  |  |  | if (self->shp_num != expected_size) { | 
| 473 | 0 |  |  |  |  |  | croak("Expected %zu records, got %zu in \"%" SVf "\"", | 
| 474 | 0 |  |  |  |  |  | expected_size, self->shp_num, SVfARG(self->shp_filename)); | 
| 475 |  |  |  |  |  |  | } | 
| 476 | 2 |  |  |  |  |  | } XCPT_TRY_END | 
| 477 |  |  |  |  |  |  |  | 
| 478 | 2 | 100 |  |  |  |  | XCPT_CATCH { | 
| 479 | 1 |  |  |  |  |  | free_self(self); | 
| 480 | 1 | 50 |  |  |  |  | XCPT_RETHROW; | 
|  |  | 0 |  |  |  |  |  | 
| 481 |  |  |  |  |  |  | } | 
| 482 |  |  |  |  |  |  |  | 
| 483 | 1 |  |  |  |  |  | RETVAL = sv_bless(newRV_noinc(newSViv(PTR2IV(self))), | 
| 484 |  |  |  |  |  |  | gv_stashsv(klass, GV_ADD)); | 
| 485 |  |  |  |  |  |  | OUTPUT: | 
| 486 |  |  |  |  |  |  | RETVAL | 
| 487 |  |  |  |  |  |  |  | 
| 488 |  |  |  |  |  |  | void | 
| 489 |  |  |  |  |  |  | time_zones_at(self, ...) | 
| 490 |  |  |  |  |  |  | Geo::Location::TimeZoneFinder self | 
| 491 |  |  |  |  |  |  | ALIAS: | 
| 492 |  |  |  |  |  |  | time_zone_at = 1 | 
| 493 |  |  |  |  |  |  | INIT: | 
| 494 | 20 |  |  |  |  |  | SV *latitude = NULL; | 
| 495 | 20 |  |  |  |  |  | SV *longitude = NULL; | 
| 496 | 20 |  |  |  |  |  | NV lat = 0.0; | 
| 497 | 20 |  |  |  |  |  | NV lon = 0.0; | 
| 498 |  |  |  |  |  |  | I32 i; | 
| 499 |  |  |  |  |  |  | const char *key; | 
| 500 |  |  |  |  |  |  | SV *value; | 
| 501 |  |  |  |  |  |  | shp_point_t location; | 
| 502 |  |  |  |  |  |  | AV *time_zones; | 
| 503 |  |  |  |  |  |  | SSize_t tz_count, tz_num; | 
| 504 |  |  |  |  |  |  | SV **svp; | 
| 505 | 20 | 50 |  |  |  |  | U8 gimme = GIMME_V; | 
| 506 |  |  |  |  |  |  | PPCODE: | 
| 507 | 20 | 50 |  |  |  |  | if ((items - 1) % 2 != 0) { | 
| 508 | 0 | 0 |  |  |  |  | warn("Odd-length list passed to %s method", | 
| 509 |  |  |  |  |  |  | (ix == 1) ? "time_zone_at" : "time_zones_at"); | 
| 510 |  |  |  |  |  |  | } | 
| 511 |  |  |  |  |  |  |  | 
| 512 | 58 | 100 |  |  |  |  | for (i = 1; i < items; i += 2) { | 
| 513 | 38 | 50 |  |  |  |  | key = SvPV_nolen_const(ST(i)); | 
| 514 | 38 |  |  |  |  |  | value = ST(i + 1); | 
| 515 | 38 | 100 |  |  |  |  | if (strEQ(key, "lat") || strEQ(key, "latitude")) { | 
|  |  | 100 |  |  |  |  |  | 
| 516 | 19 |  |  |  |  |  | latitude = value; | 
| 517 |  |  |  |  |  |  | } | 
| 518 | 19 | 100 |  |  |  |  | else if (strEQ(key, "lon") || strEQ(key, "longitude")) { | 
|  |  | 50 |  |  |  |  |  | 
| 519 | 19 |  |  |  |  |  | longitude = value; | 
| 520 |  |  |  |  |  |  | } | 
| 521 |  |  |  |  |  |  | } | 
| 522 |  |  |  |  |  |  |  | 
| 523 | 20 | 100 |  |  |  |  | if (latitude == NULL) { | 
| 524 | 1 |  |  |  |  |  | croak("The \"latitude\" parameter is mandatory"); | 
| 525 |  |  |  |  |  |  | } | 
| 526 |  |  |  |  |  |  |  | 
| 527 | 19 | 100 |  |  |  |  | if (longitude == NULL) { | 
| 528 | 1 |  |  |  |  |  | croak("The \"longitude\" parameter is mandatory"); | 
| 529 |  |  |  |  |  |  | } | 
| 530 |  |  |  |  |  |  |  | 
| 531 | 18 | 100 |  |  |  |  | if (!SvNIOK(latitude)) { | 
| 532 | 2 |  |  |  |  |  | croak("The \"latitude\" parameter %" SVf " is not a number between " | 
| 533 |  |  |  |  |  |  | "-90 and 90", SVfARG(latitude)); | 
| 534 |  |  |  |  |  |  | } | 
| 535 |  |  |  |  |  |  |  | 
| 536 | 16 | 100 |  |  |  |  | if (!SvNIOK(longitude)) { | 
| 537 | 2 |  |  |  |  |  | croak("The \"longitude\" parameter %" SVf " is not a number between " | 
| 538 |  |  |  |  |  |  | "-180 and 180", SVfARG(longitude)); | 
| 539 |  |  |  |  |  |  | } | 
| 540 |  |  |  |  |  |  |  | 
| 541 | 14 | 100 |  |  |  |  | lat = SvNV(latitude); | 
| 542 | 14 | 100 |  |  |  |  | lon = SvNV(longitude); | 
| 543 |  |  |  |  |  |  |  | 
| 544 | 14 | 50 |  |  |  |  | if (Perl_isnan(lat) || lat < -90.0 || lat > 90.0) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 545 | 2 |  |  |  |  |  | croak("The \"latitude\" parameter %" SVf " is not a number between " | 
| 546 |  |  |  |  |  |  | "-90 and 90", SVfARG(latitude)); | 
| 547 |  |  |  |  |  |  | } | 
| 548 |  |  |  |  |  |  |  | 
| 549 | 12 | 50 |  |  |  |  | if (Perl_isnan(lon) || lon < -180.0 || lon > 180.0) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 550 | 2 |  |  |  |  |  | croak("The \"longitude\" parameter %" SVf " is not a number between " | 
| 551 |  |  |  |  |  |  | "-180 and 180", SVfARG(longitude)); | 
| 552 |  |  |  |  |  |  | } | 
| 553 |  |  |  |  |  |  |  | 
| 554 | 10 |  |  |  |  |  | time_zones = newAV(); | 
| 555 |  |  |  |  |  |  |  | 
| 556 | 10 |  |  |  |  |  | location.x = lon; | 
| 557 | 10 |  |  |  |  |  | location.y = lat; | 
| 558 | 10 |  |  |  |  |  | get_special_time_zones(self, &location, time_zones); | 
| 559 | 10 | 100 |  |  |  |  | if (av_len(time_zones) < 0) { | 
| 560 | 7 |  |  |  |  |  | get_time_zones(self, &location, time_zones); | 
| 561 | 7 | 100 |  |  |  |  | if (av_len(time_zones) < 0) { | 
| 562 | 4 |  |  |  |  |  | get_time_zones_at_sea(self, &location, time_zones); | 
| 563 |  |  |  |  |  |  | } | 
| 564 |  |  |  |  |  |  | } | 
| 565 |  |  |  |  |  |  |  | 
| 566 | 10 |  |  |  |  |  | tz_count = av_len(time_zones) + 1; | 
| 567 | 47 | 100 |  |  |  |  | for (tz_num = 0; tz_num < tz_count; ++tz_num) { | 
| 568 | 38 |  |  |  |  |  | svp = av_fetch(time_zones, tz_num, 0); | 
| 569 | 38 | 50 |  |  |  |  | if (svp != NULL) { | 
| 570 | 38 | 50 |  |  |  |  | XPUSHs(SvREFCNT_inc(*svp)); | 
| 571 | 38 | 100 |  |  |  |  | if (gimme == G_SCALAR || ix == 1) { | 
|  |  | 50 |  |  |  |  |  | 
| 572 |  |  |  |  |  |  | break; | 
| 573 |  |  |  |  |  |  | } | 
| 574 |  |  |  |  |  |  | } | 
| 575 |  |  |  |  |  |  | } | 
| 576 |  |  |  |  |  |  |  | 
| 577 | 10 |  |  |  |  |  | SvREFCNT_dec((SV *) time_zones); | 
| 578 |  |  |  |  |  |  |  | 
| 579 |  |  |  |  |  |  | SV * | 
| 580 |  |  |  |  |  |  | index(self) | 
| 581 |  |  |  |  |  |  | Geo::Location::TimeZoneFinder self | 
| 582 |  |  |  |  |  |  | INIT: | 
| 583 |  |  |  |  |  |  | AV *results, *box; | 
| 584 |  |  |  |  |  |  | struct index *index; | 
| 585 |  |  |  |  |  |  | struct index_entry *entry; | 
| 586 |  |  |  |  |  |  | size_t n, i; | 
| 587 |  |  |  |  |  |  | HV *rh; | 
| 588 |  |  |  |  |  |  | CODE: | 
| 589 | 1 |  |  |  |  |  | results = (AV *) sv_2mortal((SV *) newAV()); | 
| 590 | 1 |  |  |  |  |  | index = &self->index; | 
| 591 | 1 |  |  |  |  |  | n = index->num_entries; | 
| 592 | 7 | 100 |  |  |  |  | for (i = 0; i < n; ++i) { | 
| 593 | 6 |  |  |  |  |  | entry = &index->entries[i]; | 
| 594 | 6 |  |  |  |  |  | rh = (HV *) sv_2mortal((SV *) newHV()); | 
| 595 | 6 |  |  |  |  |  | box = (AV *) sv_2mortal((SV *) newAV()); | 
| 596 | 6 |  |  |  |  |  | av_extend(box, 3); | 
| 597 | 6 |  |  |  |  |  | av_push(box, newSVnv(entry->box.x_min)); | 
| 598 | 6 |  |  |  |  |  | av_push(box, newSVnv(entry->box.y_min)); | 
| 599 | 6 |  |  |  |  |  | av_push(box, newSVnv(entry->box.x_max)); | 
| 600 | 6 |  |  |  |  |  | av_push(box, newSVnv(entry->box.y_max)); | 
| 601 | 6 |  |  |  |  |  | hv_store(rh, "bounding_box", 12, newRV_inc((SV *) box), 0); | 
| 602 | 6 |  |  |  |  |  | hv_store(rh, "file_offset", 11, newSViv(entry->file_offset), 0); | 
| 603 | 6 |  |  |  |  |  | hv_store(rh, "time_zone", 9, SvREFCNT_inc(entry->time_zone), 0); | 
| 604 | 6 |  |  |  |  |  | av_push(results, newRV_inc((SV *) rh)); | 
| 605 |  |  |  |  |  |  | } | 
| 606 | 1 |  |  |  |  |  | RETVAL = newRV_inc((SV *) results); | 
| 607 |  |  |  |  |  |  | OUTPUT: | 
| 608 |  |  |  |  |  |  | RETVAL | 
| 609 |  |  |  |  |  |  |  | 
| 610 |  |  |  |  |  |  | void | 
| 611 |  |  |  |  |  |  | DESTROY(self) | 
| 612 |  |  |  |  |  |  | Geo::Location::TimeZoneFinder self; | 
| 613 |  |  |  |  |  |  | CODE: | 
| 614 | 1 |  |  |  |  |  | free_self(self); |