From 0becaab728cf8d863bf75c4a773ffca6da8e26e1 Mon Sep 17 00:00:00 2001 From: Cam Saul <1455846+camsaul@users.noreply.github.com> Date: Tue, 3 Dec 2019 22:56:52 -0500 Subject: [PATCH] Use java.time classes everywhere (#11390) --- .dir-locals.el | 1 - dev/src/dev/test.clj | 1 - .../MetabaseApi.e2e.spec.js.snap | 22 +- .../metabase/sample_dataset/addresses.edn | 17500 ---------------- .../metabase/sample_dataset/generate.clj | 551 - .../driver/bigquery/query_processor_test.clj | 7 +- .../test/metabase/test/data/bigquery.clj | 3 +- .../druid/test/metabase/driver/druid_test.clj | 3 +- .../googleanalytics/query_processor.clj | 26 +- .../googleanalytics/query_processor_test.clj | 2 +- .../mongo/src/metabase/driver/mongo.clj | 55 +- .../metabase/driver/mongo/query_processor.clj | 22 - .../mongo/test/metabase/test/data/mongo.clj | 20 +- .../oracle/src/metabase/driver/oracle.clj | 7 +- .../test/metabase/driver/oracle_test.clj | 56 +- .../test/metabase/driver/snowflake_test.clj | 10 +- .../test/metabase/test/data/snowflake.clj | 3 +- .../src/metabase/driver/hive_like.clj | 6 +- .../sqlite/src/metabase/driver/sqlite.clj | 38 +- .../test/metabase/driver/sqlite_test.clj | 38 +- .../sqlite/test/metabase/test/data/sqlite.clj | 50 +- .../src/metabase/driver/sqlserver.clj | 22 +- .../test/metabase/driver/sqlserver_test.clj | 40 +- .../vertica/src/metabase/driver/vertica.clj | 6 +- project.clj | 9 - resources/data_readers.clj | 1 + src/metabase/api/activity.clj | 4 +- src/metabase/api/dataset.clj | 6 +- src/metabase/async/api_response.clj | 9 +- src/metabase/automagic_dashboards/core.clj | 66 +- src/metabase/automagic_dashboards/filters.clj | 6 +- src/metabase/cmd.clj | 5 +- src/metabase/cmd/dump_to_h2.clj | 2 +- src/metabase/cmd/load_from_h2.clj | 2 +- src/metabase/db.clj | 28 +- src/metabase/db/jdbc_protocols.clj | 157 + src/metabase/db/migrations.clj | 6 +- src/metabase/driver.clj | 3 +- src/metabase/driver/h2.clj | 4 +- src/metabase/driver/postgres.clj | 3 +- src/metabase/driver/sql/util/unprepare.clj | 17 - src/metabase/email/messages.clj | 26 +- src/metabase/events/last_login.clj | 3 +- src/metabase/metabot/instance.clj | 15 +- src/metabase/middleware/json.clj | 11 +- src/metabase/middleware/log.clj | 6 +- src/metabase/middleware/security.clj | 7 +- src/metabase/middleware/session.clj | 28 +- src/metabase/models.clj | 106 + src/metabase/models/activity.clj | 4 +- src/metabase/models/card.clj | 1 - src/metabase/models/collection.clj | 1 - src/metabase/models/collection_revision.clj | 9 +- src/metabase/models/dashboard.clj | 2 +- src/metabase/models/dependency.clj | 10 +- src/metabase/models/field.clj | 2 - src/metabase/models/interface.clj | 33 +- src/metabase/models/metric.clj | 2 +- .../models/metric_important_field.clj | 2 +- src/metabase/models/permissions_revision.clj | 9 +- src/metabase/models/query_execution.clj | 2 +- src/metabase/models/revision.clj | 36 +- src/metabase/models/segment.clj | 2 +- src/metabase/models/session.clj | 3 +- src/metabase/models/setting.clj | 8 +- src/metabase/models/table.clj | 7 +- src/metabase/models/task_history.clj | 6 +- src/metabase/models/user.clj | 3 +- src/metabase/models/view_log.clj | 3 +- src/metabase/plugins.clj | 54 +- src/metabase/plugins/dependencies.clj | 1 - src/metabase/plugins/lazy_loaded_driver.clj | 3 +- src/metabase/public_settings.clj | 21 +- src/metabase/pulse.clj | 15 +- src/metabase/pulse/render.clj | 22 +- src/metabase/pulse/render/body.clj | 46 +- src/metabase/pulse/render/datetime.clj | 103 +- src/metabase/pulse/render/sparkline.clj | 34 +- .../query_processor/middleware/async.clj | 16 +- .../query_processor/middleware/cache.clj | 6 +- .../middleware/cache_backend/db.clj | 14 +- .../middleware/optimize_datetime_filters.clj | 26 +- .../middleware/parameters/dates.clj | 102 +- .../parameters/native/interface.clj | 2 +- .../parameters/native/substitution.clj | 2 +- .../middleware/parameters/native/values.clj | 4 +- .../middleware/process_userland_query.clj | 6 +- src/metabase/query_processor/timezone.clj | 56 +- src/metabase/sync/analyze.clj | 6 +- .../analyze/fingerprint/fingerprinters.clj | 83 +- .../sync/analyze/fingerprint/insights.clj | 27 +- src/metabase/sync/util.clj | 47 +- src/metabase/task/follow_up_emails.clj | 79 +- src/metabase/util.clj | 103 +- src/metabase/util/date.clj | 509 - src/metabase/util/date_2.clj | 134 +- src/metabase/util/date_2/common.clj | 34 +- src/metabase/util/date_2/parse.clj | 117 +- src/metabase/util/date_2/parse/builder.clj | 34 +- src/metabase/util/files.clj | 6 +- src/metabase/util/stats.clj | 63 +- test/metabase/api/activity_test.clj | 187 +- test/metabase/api/alert_test.clj | 1 + test/metabase/api/card_test.clj | 11 +- test/metabase/api/database_test.clj | 4 +- test/metabase/api/dataset_test.clj | 3 + test/metabase/api/table_test.clj | 4 +- test/metabase/api/task_test.clj | 13 +- test/metabase/api/transform_test.clj | 6 +- .../automagic_dashboards/core_test.clj | 121 +- test/metabase/cmd/compare_h2_dbs.clj | 14 +- test/metabase/cmd/load_from_h2_test.clj | 27 +- test/metabase/db/migrations_test.clj | 7 +- test/metabase/driver/mysql_test.clj | 16 +- test/metabase/driver/postgres_test.clj | 8 +- .../driver/sql/query_processor_test.clj | 4 +- test/metabase/driver/sql_jdbc_test.clj | 64 +- test/metabase/http_client.clj | 56 +- test/metabase/metabot/instance_test.clj | 4 +- test/metabase/middleware/auth_test.clj | 6 +- test/metabase/middleware/session_test.clj | 117 +- test/metabase/models/dependency_test.clj | 7 +- test/metabase/models/session_test.clj | 25 +- test/metabase/models/setting/cache_test.clj | 7 +- test/metabase/models/setting_test.clj | 8 +- test/metabase/models/task_history_test.clj | 19 +- test/metabase/pulse/render/body_test.clj | 129 +- test/metabase/pulse/render/datetime_test.clj | 114 +- test/metabase/pulse/render/sparkline_test.clj | 22 + test/metabase/pulse_test.clj | 6 +- .../add_dimension_projections_test.clj | 6 +- .../optimize_datetime_filters_test.clj | 74 +- .../middleware/parameters/date_test.clj | 94 +- .../middleware/parameters/native_test.clj | 99 +- .../middleware/results_metadata_test.clj | 4 +- .../date_bucketing_test.clj | 29 +- .../query_processor_test/expressions_test.clj | 33 +- .../query_processor_test/failure_test.clj | 2 +- test/metabase/sync/analyze/classify_test.clj | 5 +- .../fingerprint/fingerprinters_test.clj | 102 +- .../analyze/fingerprint/insights_test.clj | 116 +- .../sync/analyze/fingerprint_test.clj | 3 +- test/metabase/sync/analyze_test.clj | 94 +- test/metabase/sync/util_test.clj | 174 +- .../sync_database/sync_dynamic_test.clj | 4 +- test/metabase/task/follow_up_emails_test.clj | 29 +- test/metabase/task/sync_databases_test.clj | 4 +- .../task/task_history_cleanup_test.clj | 75 +- .../test/data/dataset_definitions.clj | 96 +- .../dataset_definitions/daily-bird-counts.edn | 60 +- .../dataset_definitions/office-checkins.edn | 20 +- .../data/dataset_definitions/test-data.edn | 2030 +- test/metabase/test/data/impl.clj | 9 +- test/metabase/test/data/interface.clj | 9 +- .../metabase/test/data/sql_jdbc/load_data.clj | 12 +- test/metabase/test/initialize.clj | 46 +- test/metabase/test/initialize/web_server.clj | 30 +- test/metabase/test/util.clj | 27 +- test/metabase/test/util/timezone.clj | 55 +- test/metabase/util/date_2_test.clj | 43 +- test/metabase/util/date_test.clj | 111 - 161 files changed, 3582 insertions(+), 21862 deletions(-) delete mode 100644 lein-commands/sample-dataset/metabase/sample_dataset/addresses.edn delete mode 100644 lein-commands/sample-dataset/metabase/sample_dataset/generate.clj create mode 100644 resources/data_readers.clj create mode 100644 src/metabase/db/jdbc_protocols.clj create mode 100644 src/metabase/models.clj delete mode 100644 src/metabase/util/date.clj create mode 100644 test/metabase/pulse/render/sparkline_test.clj delete mode 100644 test/metabase/util/date_test.clj diff --git a/.dir-locals.el b/.dir-locals.el index dfd4f9b2597..1d6a066efc5 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -21,7 +21,6 @@ (let-404 1) (match 1) (merge-with 1) - (with-redefs-fn 1) (p.types/defprotocol+ '(1 (:defn))) (p.types/def-abstract-type '(1 (:defn))) (p.types/deftype+ '(2 nil nil (:defn))) diff --git a/dev/src/dev/test.clj b/dev/src/dev/test.clj index 18535d206bf..2e19a0f09bd 100644 --- a/dev/src/dev/test.clj +++ b/dev/src/dev/test.clj @@ -106,7 +106,6 @@ call-with-paused-query discard-setting-changes doall-recursive - exception-and-message is-uuid-string? metabase-logger postwalk-pred diff --git a/frontend/test/metabase/services/__snapshots__/MetabaseApi.e2e.spec.js.snap b/frontend/test/metabase/services/__snapshots__/MetabaseApi.e2e.spec.js.snap index 075941a5172..f2846cd3cd8 100644 --- a/frontend/test/metabase/services/__snapshots__/MetabaseApi.e2e.spec.js.snap +++ b/frontend/test/metabase/services/__snapshots__/MetabaseApi.e2e.spec.js.snap @@ -434,8 +434,8 @@ Object { }, "type": Object { "type/DateTime": Object { - "earliest": "2016-05-01T01:56:13.352Z", - "latest": "2020-04-19T21:07:15.657Z", + "earliest": "2016-04-30T18:56:13.352", + "latest": "2020-04-19T14:07:15.657", }, }, }, @@ -1573,8 +1573,8 @@ Object { }, "type": Object { "type/DateTime": Object { - "earliest": "1958-04-26T08:00:00.000Z", - "latest": "2000-04-03T07:00:00.000Z", + "earliest": "1958-04-26", + "latest": "2000-04-03", }, }, }, @@ -1793,8 +1793,8 @@ Object { }, "type": Object { "type/DateTime": Object { - "earliest": "2016-04-20T04:35:18.752Z", - "latest": "2019-04-19T21:06:27.300Z", + "earliest": "2016-04-19T21:35:18.752", + "latest": "2019-04-19T14:06:27.3", }, }, }, @@ -2778,8 +2778,8 @@ Object { }, "type": Object { "type/DateTime": Object { - "earliest": "2016-04-27T02:29:55.147Z", - "latest": "2019-04-15T20:34:19.931Z", + "earliest": "2016-04-26T19:29:55.147", + "latest": "2019-04-15T13:34:19.931", }, }, }, @@ -3443,7 +3443,7 @@ Object { }, "type": Object { "type/Text": Object { - "average-length": 177.41996402877697, + "average-length": 182.37589928057554, "percent-email": 0, "percent-json": 0, "percent-url": 0, @@ -3626,8 +3626,8 @@ Object { }, "type": Object { "type/DateTime": Object { - "earliest": "2016-06-03T07:37:05.818Z", - "latest": "2020-04-19T21:15:25.677Z", + "earliest": "2016-06-03T00:37:05.818", + "latest": "2020-04-19T14:15:25.677", }, }, }, diff --git a/lein-commands/sample-dataset/metabase/sample_dataset/addresses.edn b/lein-commands/sample-dataset/metabase/sample_dataset/addresses.edn deleted file mode 100644 index e175fc72821..00000000000 --- a/lein-commands/sample-dataset/metabase/sample_dataset/addresses.edn +++ /dev/null @@ -1,17500 +0,0 @@ -({:lat 40.71314890000001, - :lon -98.5259864, - :house-number "9611-9809", - :street "West Rosedale Road", - :city "Wood River", - :state-abbrev "NE", - :zip "68883"} - {:lat 41.5813224, - :lon -92.6991321, - :house-number "101", - :street "4th Street", - :city "Searsboro", - :state-abbrev "IA", - :zip "50242"} - {:lat 46.11973039999999, - :lon -92.8416108, - :house-number "29494", - :street "Anderson Drive", - :city "Sandstone", - :state-abbrev "MN", - :zip "55072"} - {:lat 37.9202933, - :lon -104.9726909, - :house-number "2-7900", - :street "Cuerno Verde Road", - :city "Rye", - :state-abbrev "CO", - :zip "81069"} - {:lat 42.348954, - :lon -77.056681, - :house-number "761", - :street "Fish Hill Road", - :city "Beaver Dams", - :state-abbrev "NY", - :zip "14812"} - {:lat 30.1514772, - :lon -92.4861786, - :house-number "1243", - :street "West Whitney Street", - :city "Morse", - :state-abbrev "LA", - :zip "70559"} - {:lat 31.2341055, - :lon -88.5856948, - :house-number "630", - :street "Coaker Road", - :city "Leakesville", - :state-abbrev "MS", - :zip "39451"} - {:lat 37.43472089999999, - :lon -94.6426865, - :house-number "1167", - :street "East 570th Avenue", - :city "Pittsburg", - :state-abbrev "KS", - :zip "66762"} - {:lat 42.29790209999999, - :lon -95.4673587, - :house-number "5816-5894", - :street "280th Street", - :city "Ida Grove", - :state-abbrev "IA", - :zip "51445"} - {:lat 40.8006673, - :lon -83.2838391, - :house-number "13081-13217", - :street "Main Street", - :city "Upper Sandusky", - :state-abbrev "OH", - :zip "43351"} - {:lat 42.1394217, - :lon -93.982366, - :house-number "495", - :street "Juniper Road", - :city "Pilot Mound", - :state-abbrev "IA", - :zip "50223"} - {:lat 44.9564152, - :lon -97.2287266, - :house-number "16701-16743", - :street "449th Avenue", - :city "Florence", - :state-abbrev "SD", - :zip "57235"} - {:lat 33.08172, - :lon -116.661853, - :house-number "2993", - :street "Hoskings Ranch Road", - :city "Santa Ysabel", - :state-abbrev "CA", - :zip "92070"} - {:lat 39.6485802, - :lon -121.9343322, - :house-number "3964", - :street "Chico River Road", - :city "Chico", - :state-abbrev "CA", - :zip "95928"} - {:lat 35.1080351, - :lon -92.0101859, - :house-number "258", - :street "Opal Road", - :city "El Paso", - :state-abbrev "AR", - :zip "72045"} - {:lat 32.7042678, - :lon -87.54135959999999, - :house-number "3234", - :street "County Road 7", - :city "Greensboro", - :state-abbrev "AL", - :zip "36744"} - {:lat 42.5048529, - :lon -124.1878367, - :house-number "6111", - :street "Rogue Riv", - :city "Gold Beach", - :state-abbrev "OR", - :zip "97444"} - {:lat 45.554885, - :lon -122.5277436, - :house-number "13116", - :street "Northeast Sandy Boulevard", - :city "Portland", - :state-abbrev "OR", - :zip "97230"} - {:lat 42.3222669, - :lon -123.324017, - :house-number "1460", - :street "Grays Creek Road", - :city "Grants Pass", - :state-abbrev "OR", - :zip "97527"} - {:lat 41.466973, - :lon -95.1012128, - :house-number "52737", - :street "570th Street", - :city "Marne", - :state-abbrev "IA", - :zip "51552"} - {:lat 40.9764889, - :lon -98.13254409999999, - :house-number "2002-2078", - :street "North J Road", - :city "Phillips", - :state-abbrev "NE", - :zip "68865"} - {:lat 38.5063378, - :lon -122.1878842, - :house-number "1165", - :street "Rimrock Drive", - :city "Napa", - :state-abbrev "CA", - :zip "94558"} - {:lat 33.0854659, - :lon -81.67635899999999, - :house-number "2003", - :street "Brigham Landing Road", - :city "Girard", - :state-abbrev "GA", - :zip "30426"} - {:lat 47.1794946, - :lon -93.6321358, - :house-number "20930", - :street "Sugar Hills Road", - :city "Cohasset", - :state-abbrev "MN", - :zip "55721"} - {:lat 36.0479853, - :lon -94.20265719999999, - :house-number "1201-2099", - :street "Plumberosa Drive", - :city "Fayetteville", - :state-abbrev "AR", - :zip "72701"} - {:lat 41.4720902, - :lon -118.346273, - :house-number "19480", - :street "Happy Creek Road", - :city "Winnemucca", - :state-abbrev "NV", - :zip "89445"} - {:lat 41.1278979, - :lon -83.5359594, - :house-number "2398-2558", - :street "Township Highway 249", - :city "Arcadia", - :state-abbrev "OH", - :zip "44804"} - {:lat 34.4325826, - :lon -88.5702919, - :house-number "355", - :street "Road 2538", - :city "Baldwyn", - :state-abbrev "MS", - :zip "38824"} - {:lat 39.6955024, - :lon -82.5027112, - :house-number "5125", - :street "Duffy Road Southeast", - :city "Lancaster", - :state-abbrev "OH", - :zip "43130"} - {:lat 34.3125434, - :lon -91.20821, - :house-number "13-99", - :street "Deberry Levee Road", - :city "DeWitt", - :state-abbrev "AR", - :zip "72042"} - {:lat 43.80686660000001, - :lon -91.6359614, - :house-number "2303", - :street "Christianson Hill Road", - :city "Houston", - :state-abbrev "MN", - :zip "55943"} - {:lat 37.94777029999999, - :lon -104.8778192, - :house-number "6101", - :street "County Road CC67", - :city "Pueblo", - :state-abbrev "CO", - :zip "81004"} - {:lat 40.3372992, - :lon -106.7981696, - :house-number "33430", - :street "Danver Trail", - :city "Steamboat Springs", - :state-abbrev "CO", - :zip "80487"} - {:lat 38.656887, - :lon -77.975854, - :house-number "3349", - :street "Holly Springs Road", - :city "Amissville", - :state-abbrev "VA", - :zip "20106"} - {:lat 42.5616569, - :lon -88.677537, - :house-number "N1962", - :street "County Road K", - :city "Sharon", - :state-abbrev "WI", - :zip "53585"} - {:lat 38.6232452, - :lon -98.8225441, - :house-number "244-298", - :street "Northwest 180 Road", - :city "Hoisington", - :state-abbrev "KS", - :zip "67544"} - {:lat 32.3735889, - :lon -111.03742, - :house-number "3210-3298", - :street "West Overton Road", - :city "Tucson", - :state-abbrev "AZ", - :zip "85742"} - {:lat 38.4920821, - :lon -82.0002696, - :house-number "155", - :street "Cow Creek Road", - :city "Hurricane", - :state-abbrev "WV", - :zip "25526"} - {:lat 43.9393467, - :lon -96.1592993, - :house-number "618", - :street "150th Avenue", - :city "Edgerton", - :state-abbrev "MN", - :zip "56128"} - {:lat 29.99592, - :lon -97.5590129, - :house-number "171", - :street "Olive Oyle Lane", - :city "Dale", - :state-abbrev "TX", - :zip "78616"} - {:lat 41.539616, - :lon -93.874784, - :house-number "31619", - :street "Silverado Lane", - :city "Waukee", - :state-abbrev "IA", - :zip "50263"} - {:lat 44.10684560000001, - :lon -75.9958097, - :house-number "30379-30383", - :street "Herbrecht Road", - :city "Watertown", - :state-abbrev "NY", - :zip "13601"} - {:lat 42.43880739999999, - :lon -92.6713936, - :house-number "18001-18999", - :street "North Morrion", - :city "Dike", - :state-abbrev "IA", - :zip "50624"} - {:lat 43.0246186, - :lon -94.6294178, - :house-number "4251-4389", - :street "485th Avenue", - :city "Mallard", - :state-abbrev "IA", - :zip "50562"} - {:lat 41.3221172, - :lon -91.47486099999999, - :house-number "28001-28599", - :street "180th Street", - :city "Columbus Junction", - :state-abbrev "IA", - :zip "52738"} - {:lat 44.8540315, - :lon -84.87710150000001, - :house-number "15000-15312", - :street "Crooked Road Northeast", - :city "Kalkaska", - :state-abbrev "MI", - :zip "49646"} - {:lat 35.337122, - :lon -111.3635, - :house-number "111", - :street "Leupp Road", - :city "Flagstaff", - :state-abbrev "AZ", - :zip "86004"} - {:lat 39.9879628, - :lon -97.1458337, - :house-number "1260", - :street "29th Road", - :city "Morrowville", - :state-abbrev "KS", - :zip "66958"} - {:lat 37.1382857, - :lon -94.11219600000001, - :house-number "6043", - :street "County Road 30", - :city "Reeds", - :state-abbrev "MO", - :zip "64859"} - {:lat 48.2384345, - :lon -97.86937549999999, - :house-number "12751-12799", - :street "57th Street Northeast", - :city "Fordville", - :state-abbrev "ND", - :zip "58231"} - {:lat 33.493444, - :lon -89.8906564, - :house-number "21421", - :street "U.S. 82", - :city "Carrollton", - :state-abbrev "MS", - :zip "38917"} - {:lat 34.057083, - :lon -83.81534099999999, - :house-number "1110", - :street "Puckett Road", - :city "Auburn", - :state-abbrev "GA", - :zip "30011"} - {:lat 34.272145, - :lon -89.185783, - :house-number "1380", - :street "Juba Road", - :city "Pontotoc", - :state-abbrev "MS", - :zip "38863"} - {:lat 42.324103, - :lon -96.74542609999999, - :house-number "87245", - :street "590 Avenue", - :city "Waterbury", - :state-abbrev "NE", - :zip "68785"} - {:lat 32.1412409, - :lon -85.6383022, - :house-number "373", - :street "Lee Loop Road", - :city "Union Springs", - :state-abbrev "AL", - :zip "36089"} - {:lat 40.5974724, - :lon -102.5311932, - :house-number "22842", - :street "County Road 15", - :city "Haxtun", - :state-abbrev "CO", - :zip "80731"} - {:lat 46.578927, - :lon -116.537937, - :house-number "35378", - :street "South Road", - :city "Kendrick", - :state-abbrev "ID", - :zip "83537"} - {:lat 37.8205743, - :lon -87.2486685, - :house-number "174", - :street "Kentucky 1554", - :city "Owensboro", - :state-abbrev "KY", - :zip "42301"} - {:lat 37.4230302, - :lon -105.3720874, - :house-number "3405", - :street "Brittain Road", - :city "Fort Garland", - :state-abbrev "CO", - :zip "81133"} - {:lat 38.111944, - :lon -82.5399396, - :house-number "1826", - :street "Paddle Creek Road", - :city "Fort Gay", - :state-abbrev "WV", - :zip "25514"} - {:lat 35.69917, - :lon -81.017265, - :house-number "7100", - :street "Hudson Chapel Road", - :city "Catawba", - :state-abbrev "NC", - :zip "28609"} - {:lat 27.872096, - :lon -82.61317000000001, - :house-number "12055", - :street "Gandy Boulevard North", - :city "Saint Petersburg", - :state-abbrev "FL", - :zip "33702"} - {:lat 43.21093399999999, - :lon -95.24696499999999, - :house-number "1725", - :street "300th Street", - :city "Spencer", - :state-abbrev "IA", - :zip "51301"} - {:lat 42.723496, - :lon -75.856499, - :house-number "207", - :street "Grenadier Drive", - :city "DeRuyter", - :state-abbrev "NY", - :zip "13052"} - {:lat 44.7004251, - :lon -102.1931175, - :house-number "18630", - :street "Old Marcus Road", - :city "Howes", - :state-abbrev "SD", - :zip "57748"} - {:lat 43.763995, - :lon -75.38189799999999, - :house-number "6568", - :street "Pine Grove Road", - :city "Glenfield", - :state-abbrev "NY", - :zip "13343"} - {:lat 56.26086710000001, - :lon -158.7019256, - :house-number "19", - :street "Riverfront Drive C Street", - :city "Chignik Lake", - :state-abbrev "AK", - :zip "99548"} - {:lat 37.89017339999999, - :lon -87.1323259, - :house-number "449-499", - :street "County Road 400 West", - :city "Rockport", - :state-abbrev "IN", - :zip "47635"} - {:lat 42.3420983, - :lon -96.0377865, - :house-number "2508-2520", - :street "Hancock Avenue", - :city "Moville", - :state-abbrev "IA", - :zip "51039"} - {:lat 42.1362931, - :lon -99.8663091, - :house-number "84975", - :street "Nebraska 7", - :city "Ainsworth", - :state-abbrev "NE", - :zip "69210"} - {:lat 44.500381, - :lon -109.2863821, - :house-number "3801-4099", - :street "North Fork Highway", - :city "Cody", - :state-abbrev "WY", - :zip "82414"} - {:lat 30.604401, - :lon -85.02780299999999, - :house-number "28367", - :street "Florida 69", - :city "Grand Ridge", - :state-abbrev "FL", - :zip "32442"} - {:lat 42.5549457, - :lon -72.6583358, - :house-number "28-50", - :street "Hawks Road", - :city "Shelburne Falls", - :state-abbrev "MA", - :zip "01370"} - {:lat 39.6293759, - :lon -85.3744045, - :house-number "350", - :street "East North Street", - :city "Rushville", - :state-abbrev "IN", - :zip "46173"} - {:lat 43.1848428, - :lon -100.033216, - :house-number "30682", - :street "291st Street", - :city "Winner", - :state-abbrev "SD", - :zip "57580"} - {:lat 32.9558013, - :lon -89.0665798, - :house-number "600", - :street "Hight Moore Road", - :city "Noxapater", - :state-abbrev "MS", - :zip "39346"} - {:lat 37.1228826, - :lon -83.99708249999999, - :house-number "1436", - :street "Tom Cat Trail", - :city "London", - :state-abbrev "KY", - :zip "40741"} - {:lat 45.838178, - :lon -111.495663, - :house-number "4333", - :street "Madison Road", - :city "Three Forks", - :state-abbrev "MT", - :zip "59752"} - {:lat 28.412059, - :lon -81.43779599999999, - :house-number "4797", - :street "Central Florida Parkway", - :city "Orlando", - :state-abbrev "FL", - :zip "32821"} - {:lat 33.2513235, - :lon -95.88581839999999, - :house-number "101-1299", - :street "Mosley Street", - :city "Commerce", - :state-abbrev "TX", - :zip "75428"} - {:lat 48.14901099999999, - :lon -122.137382, - :house-number "6913", - :street "168th Street Northeast", - :city "Arlington", - :state-abbrev "WA", - :zip "98223"} - {:lat 36.2020785, - :lon -94.4309788, - :house-number "15079-15327", - :street "Readings Road", - :city "Siloam Springs", - :state-abbrev "AR", - :zip "72761"} - {:lat 48.6052737, - :lon -109.2199464, - :house-number "4", - :street "Hc 69", - :city "Chinook", - :state-abbrev "MT", - :zip "59523"} - {:lat 40.8042019, - :lon -86.01402499999999, - :house-number "2787", - :street "North 300 East", - :city "Peru", - :state-abbrev "IN", - :zip "46970"} - {:lat 42.6392077, - :lon -72.50279499999999, - :house-number "21-89", - :street "Lyons Hill Road", - :city "Gill", - :state-abbrev "MA", - :zip "01354"} - {:lat 40.29595, - :lon -122.440831, - :house-number "17300", - :street "View Drive", - :city "Cottonwood", - :state-abbrev "CA", - :zip "96022"} - {:lat 42.167971, - :lon -121.867448, - :house-number "9665", - :street "Pat Drive", - :city "Klamath Falls", - :state-abbrev "OR", - :zip "97601"} - {:lat 41.350309, - :lon -123.84852, - :house-number "2", - :street "Site 7", - :city "Hoopa", - :state-abbrev "CA", - :zip "95546"} - {:lat 43.5903545, - :lon -91.33149290000001, - :house-number "17779", - :street "Walnut Road", - :city "Brownsville", - :state-abbrev "MN", - :zip "55919"} - {:lat 34.505161, - :lon -93.006959, - :house-number "2190", - :street "East Grand Avenue", - :city "Hot Springs", - :state-abbrev "AR", - :zip "71901"} - {:lat 31.36935089999999, - :lon -103.5827313, - :house-number "292-386", - :street "County Road 206", - :city "Pecos", - :state-abbrev "TX", - :zip "79772"} - {:lat 43.0570164, - :lon -97.9539025, - :house-number "29883", - :street "412th Avenue", - :city "Avon", - :state-abbrev "SD", - :zip "57315"} - {:lat 61.37213, - :lon -150.098354, - :house-number "25890", - :street "Holstein Avenue", - :city "Wasilla", - :state-abbrev "AK", - :zip "99654"} - {:lat 44.7873093, - :lon -120.934635, - :house-number "11105", - :street "North Old Highway 97", - :city "Madras", - :state-abbrev "OR", - :zip "97741"} - {:lat 47.4154041, - :lon -117.7767551, - :house-number "25611", - :street "South Carman Road", - :city "Cheney", - :state-abbrev "WA", - :zip "99004"} - {:lat 33.2537345, - :lon -92.1484057, - :house-number "839", - :street "Bradley 60 Road", - :city "Hermitage", - :state-abbrev "AR", - :zip "71647"} - {:lat 36.64563400000001, - :lon -120.00331, - :house-number "11821", - :street "West Lincoln Avenue", - :city "Fresno", - :state-abbrev "CA", - :zip "93706"} - {:lat 38.8234, - :lon -82.6755, - :house-number "2329", - :street "Jackson Fork Road", - :city "South Webster", - :state-abbrev "OH", - :zip "45682"} - {:lat 46.331827, - :lon -116.2924427, - :house-number "2160", - :street "Settler Road", - :city "Craigmont", - :state-abbrev "ID", - :zip "83523"} - {:lat 43.7130017, - :lon -75.43909099999999, - :house-number "6088", - :street "Meiss Road", - :city "Glenfield", - :state-abbrev "NY", - :zip "13343"} - {:lat 38.0319159, - :lon -94.2752332, - :house-number "3118", - :street "South 1938 Road", - :city "Rich Hill", - :state-abbrev "MO", - :zip "64779"} - {:lat 46.6588404, - :lon -113.6540732, - :house-number "550", - :street "Rock Creek Road", - :city "Clinton", - :state-abbrev "MT", - :zip "59825"} - {:lat 47.6128237, - :lon -96.9681621, - :house-number "16780", - :street "14th Street Northeast", - :city "Buxton", - :state-abbrev "ND", - :zip "58218"} - {:lat 35.598895, - :lon -84.83532799999999, - :house-number "6183", - :street "Old Dixie Highway", - :city "Evensville", - :state-abbrev "TN", - :zip "37332"} - {:lat 39.6135899, - :lon -122.5711026, - :house-number "3135", - :street "Sanhedrin Road", - :city "Elk Creek", - :state-abbrev "CA", - :zip "95939"} - {:lat 39.8863642, - :lon -86.5583394, - :house-number "1701-1999", - :street "West County Road 850 North", - :city "Lizton", - :state-abbrev "IN", - :zip "46149"} - {:lat 42.7814427, - :lon -94.8164443, - :house-number "48502-48654", - :street "150th Avenue", - :city "Pocahontas", - :state-abbrev "IA", - :zip "50574"} - {:lat 33.509668, - :lon -96.032372, - :house-number "5706", - :street "Farm to Market 1743", - :city "Windom", - :state-abbrev "TX", - :zip "75492"} - {:lat 40.919299, - :lon -81.545908, - :house-number "271", - :street "West Comet Road", - :city "Clinton", - :state-abbrev "OH", - :zip "44216"} - {:lat 40.891452, - :lon -87.290014, - :house-number "5361", - :street "East 700 South", - :city "Brook", - :state-abbrev "IN", - :zip "47922"} - {:lat 30.0178145, - :lon -93.0878386, - :house-number "521-599", - :street "Louisiana 27", - :city "Bell City", - :state-abbrev "LA", - :zip "70630"} - {:lat 39.2802275, - :lon -122.1971163, - :house-number "370", - :street "North Street", - :city "Maxwell", - :state-abbrev "CA", - :zip "95955"} - {:lat 48.65539769999999, - :lon -104.9287866, - :house-number "1711", - :street "Welliver Road", - :city "Redstone", - :state-abbrev "MT", - :zip "59257"} - {:lat 40.922515, - :lon -74.654822, - :house-number "1 A", - :street "Point Pleasant Road", - :city "Hopatcong", - :state-abbrev "NJ", - :zip "07843"} - {:lat 29.18861, - :lon -95.4850699, - :house-number "28161", - :street "FM 521 Road", - :city "Angleton", - :state-abbrev "TX", - :zip "77515"} - {:lat 46.13674779999999, - :lon -106.4627731, - :house-number "2020-2026", - :street "Rosebud Creek Road", - :city "Forsyth", - :state-abbrev "MT", - :zip "59327"} - {:lat 41.6020003, - :lon -80.32627610000002, - :house-number "10237", - :street "Pennsylvania 285", - :city "Conneaut Lake", - :state-abbrev "PA", - :zip "16316"} - {:lat 31.3799326, - :lon -94.5245865, - :house-number "870", - :street "Marions Ferry Road", - :city "Huntington", - :state-abbrev "TX", - :zip "75949"} - {:lat 34.2667033, - :lon -83.7451529, - :house-number "2945-2965", - :street "Gillsville Highway", - :city "Gainesville", - :state-abbrev "GA", - :zip "30507"} - {:lat 42.593775, - :lon -72.021141, - :house-number "538", - :street "Clark Street", - :city "Gardner", - :state-abbrev "MA", - :zip "01440"} - {:lat 43.3301989, - :lon -97.8519453, - :house-number "41706", - :street "280th Street", - :city "Tripp", - :state-abbrev "SD", - :zip "57376"} - {:lat 34.75105, - :lon -119.427429, - :house-number "31541", - :street "California 33", - :city "Maricopa", - :state-abbrev "CA", - :zip "93252"} - {:lat 41.4658789, - :lon -74.9888927, - :house-number "583", - :street "Pennsylvania 590", - :city "Lackawaxen", - :state-abbrev "PA", - :zip "18435"} - {:lat 47.8845232, - :lon -97.6237067, - :house-number "1451-1499", - :street "37th Street Northeast", - :city "Larimore", - :state-abbrev "ND", - :zip "58251"} - {:lat 46.3580329, - :lon -87.829121, - :house-number "4801", - :street "Road Cce", - :city "Ishpeming", - :state-abbrev "MI", - :zip "49849"} - {:lat 31.921767, - :lon -98.324061, - :house-number "1188", - :street "Farm to Market Road 1702", - :city "Dublin", - :state-abbrev "TX", - :zip "76446"} - {:lat 41.1490825, - :lon -87.17967300000001, - :house-number "9698-9912", - :street "North 700 West", - :city "De Motte", - :state-abbrev "IN", - :zip "46310"} - {:lat 37.703053, - :lon -106.37448, - :house-number "19045", - :street "County Road 15", - :city "Del Norte", - :state-abbrev "CO", - :zip "81132"} - {:lat 37.2630179, - :lon -86.1622465, - :house-number "8235", - :street "Nolin Dam Road", - :city "Mammoth Cave", - :state-abbrev "KY", - :zip "42259"} - {:lat 43.153337, - :lon -105.019411, - :house-number "2919", - :street "Walker Creek Road", - :city "Douglas", - :state-abbrev "WY", - :zip "82633"} - {:lat 38.5538818, - :lon -78.85508659999999, - :house-number "5132", - :street "Wengers Mill Road", - :city "Linville", - :state-abbrev "VA", - :zip "22834"} - {:lat 39.561936, - :lon -80.161913, - :house-number "228", - :street "Abpp Drive", - :city "Grant Town", - :state-abbrev "WV", - :zip "26574"} - {:lat 47.6208379, - :lon -105.3887932, - :house-number "281-699", - :street "East Duck Creek Road", - :city "Circle", - :state-abbrev "MT", - :zip "59215"} - {:lat 47.3232465, - :lon -119.309409, - :house-number "8001-8783", - :street "Road 16 Northeast", - :city "Moses Lake", - :state-abbrev "WA", - :zip "98837"} - {:lat 41.414166, - :lon -81.9847444, - :house-number "5509-5501,5500-5508", - :street "McKinley Avenue", - :city "North Ridgeville", - :state-abbrev "OH", - :zip "44039"} - {:lat 30.9388436, - :lon -96.07014129999999, - :house-number "9967-10311", - :street "Jinkins Road", - :city "North Zulch", - :state-abbrev "TX", - :zip "77872"} - {:lat 33.4107005, - :lon -93.8872518, - :house-number "261", - :street "Pr 1138", - :city "Texarkana", - :state-abbrev "AR", - :zip "71854"} - {:lat 44.297511, - :lon -95.6824956, - :house-number "3001-3099", - :street "170th Street", - :city "Tracy", - :state-abbrev "MN", - :zip "56175"} - {:lat 29.11341699999999, - :lon -97.021, - :house-number "15024", - :street "Farm to Market 682", - :city "Yoakum", - :state-abbrev "TX", - :zip "77995"} - {:lat 30.3817696, - :lon -91.1954073, - :house-number "2933", - :street "Sarpy Avenue", - :city "Baton Rouge", - :state-abbrev "LA", - :zip "70820"} - {:lat 48.205763, - :lon -94.5749173, - :house-number "22087", - :street "Fishermans Haven Road Northeast", - :city "Waskish", - :state-abbrev "MN", - :zip "56685"} - {:lat 45.4779624, - :lon -92.5522438, - :house-number "1855", - :street "190th Street", - :city "Centuria", - :state-abbrev "WI", - :zip "54824"} - {:lat 42.3285929, - :lon -97.01779409999999, - :house-number "86229-86265", - :street "576th Avenue", - :city "Wayne", - :state-abbrev "NE", - :zip "68787"} - {:lat 38.827956, - :lon -83.75506399999999, - :house-number "6956", - :street "Mount Aire Road", - :city "Russellville", - :state-abbrev "OH", - :zip "45168"} - {:lat 44.728183, - :lon -83.4251333, - :house-number "2491", - :street "Miller Road", - :city "Lincoln", - :state-abbrev "MI", - :zip "48742"} - {:lat 42.7990559, - :lon -82.66714689999999, - :house-number "8840", - :street "Saint Clair Highway", - :city "Casco", - :state-abbrev "MI", - :zip "48064"} - {:lat 35.166327, - :lon -118.477387, - :house-number "19500", - :street "Dovetail Court", - :city "Tehachapi", - :state-abbrev "CA", - :zip "93561"} - {:lat 33.6872507, - :lon -90.2063275, - :house-number "268", - :street "County Road 454", - :city "Greenwood", - :state-abbrev "MS", - :zip "38930"} - {:lat 37.5746079, - :lon -78.47260849999999, - :house-number "1349", - :street "Prison Road", - :city "Dillwyn", - :state-abbrev "VA", - :zip "23936"} - {:lat 32.682146, - :lon -79.891785, - :house-number "1746", - :street "East Ashley Avenue", - :city "Folly Beach", - :state-abbrev "SC", - :zip "29439"} - {:lat 42.6071945, - :lon -105.5102213, - :house-number "820-872", - :street "Poison Lake Road", - :city "Douglas", - :state-abbrev "WY", - :zip "82633"} - {:lat 46.0369948, - :lon -96.75492559999999, - :house-number "17401-17499", - :street "95th Street Southeast", - :city "Fairmount", - :state-abbrev "ND", - :zip "58030"} - {:lat 44.9074009, - :lon -92.65948999999999, - :house-number "314", - :street "Wisconsin 35", - :city "River Falls", - :state-abbrev "WI", - :zip "54022"} - {:lat 40.5479816, - :lon -105.4740886, - :house-number "3331-3589", - :street "Ballard Road", - :city "Bellvue", - :state-abbrev "CO", - :zip "80512"} - {:lat 37.7530823, - :lon -87.9231926, - :house-number "5036", - :street "Airline Road", - :city "Uniontown", - :state-abbrev "KY", - :zip "42461"} - {:lat 39.7320556, - :lon -90.2451991, - :house-number "212", - :street "Park Street", - :city "Jacksonville", - :state-abbrev "IL", - :zip "62650"} - {:lat 35.9255799, - :lon -76.302166, - :house-number "1343", - :street "Davenport Road", - :city "Columbia", - :state-abbrev "NC", - :zip "27925"} - {:lat 36.8939065, - :lon -89.78379319999999, - :house-number "19736-19746", - :street "County Road 581", - :city "Essex", - :state-abbrev "MO", - :zip "63846"} - {:lat 41.8323487, - :lon -91.2776887, - :house-number "790", - :street "Echo Avenue", - :city "Mechanicsville", - :state-abbrev "IA", - :zip "52306"} - {:lat 40.916432, - :lon -86.097224, - :house-number "10360", - :street "North 100 West", - :city "Macy", - :state-abbrev "IN", - :zip "46951"} - {:lat 32.782583, - :lon -85.3869612, - :house-number "4119", - :street "Chambers County 173", - :city "La Fayette", - :state-abbrev "AL", - :zip "36862"} - {:lat 41.1256993, - :lon -82.8288503, - :house-number "6738-6772", - :street "Ohio 162", - :city "Attica", - :state-abbrev "OH", - :zip "44807"} - {:lat 41.31672040000001, - :lon -101.013686, - :house-number "26046", - :street "West North River Road", - :city "Hershey", - :state-abbrev "NE", - :zip "69143"} - {:lat 47.0765723, - :lon -93.5083726, - :house-number "13271", - :street "West Splithand Road", - :city "Grand Rapids", - :state-abbrev "MN", - :zip "55744"} - {:lat 31.2316415, - :lon -84.5972409, - :house-number "735", - :street "Kelley Road", - :city "Colquitt", - :state-abbrev "GA", - :zip "39837"} - {:lat 45.36304, - :lon -84.30846, - :house-number "9011", - :street "Kisser Road", - :city "Onaway", - :state-abbrev "MI", - :zip "49765"} - {:lat 35.822889, - :lon -81.574767, - :house-number "4126", - :street "Smokey Creek Road", - :city "Lenoir", - :state-abbrev "NC", - :zip "28645"} - {:lat 30.3280707, - :lon -96.7380668, - :house-number "9872", - :street "County Road 132", - :city "Somerville", - :state-abbrev "TX", - :zip "77879"} - {:lat 40.9746726, - :lon -72.1968189, - :house-number "154-156", - :street "Cedar Street", - :city "East Hampton", - :state-abbrev "NY", - :zip "11937"} - {:lat 45.82408, - :lon -104.30909, - :house-number "671", - :street "Prairie Dale Road", - :city "Ekalaka", - :state-abbrev "MT", - :zip "59324"} - {:lat 48.88848369999999, - :lon -96.6728171, - :house-number "3399", - :street "360th Avenue", - :city "Lancaster", - :state-abbrev "MN", - :zip "56735"} - {:lat 32.3125762, - :lon -102.3958822, - :house-number "1901-1999", - :street "Telephone Road", - :city "Andrews", - :state-abbrev "TX", - :zip "79714"} - {:lat 41.16777949999999, - :lon -82.43573289999999, - :house-number "1246", - :street "Jarvis Road", - :city "Wakeman", - :state-abbrev "OH", - :zip "44889"} - {:lat 34.8981079, - :lon -114.783128, - :house-number "131764-132898", - :street "Homer-Klinefelter Road", - :city "Needles", - :state-abbrev "CA", - :zip "92363"} - {:lat 41.977433, - :lon -94.06967730000001, - :house-number "496", - :street "260th Street", - :city "Ogden", - :state-abbrev "IA", - :zip "50212"} - {:lat 42.0529562, - :lon -85.51435, - :house-number "22711", - :street "Davis Drive", - :city "Three Rivers", - :state-abbrev "MI", - :zip "49093"} - {:lat 42.1728599, - :lon -89.7458849, - :house-number "17411", - :street "Shannon Route", - :city "Shannon", - :state-abbrev "IL", - :zip "61078"} - {:lat 42.012186, - :lon -79.046403, - :house-number "12759", - :street "Gurnsey Hollow Road", - :city "Frewsburg", - :state-abbrev "NY", - :zip "14738"} - {:lat 31.6307641, - :lon -91.5771343, - :house-number "253", - :street "Par Road 2-75", - :city "Ferriday", - :state-abbrev "LA", - :zip "71334"} - {:lat 37.17645479999999, - :lon -94.0074102, - :house-number "7059", - :street "Highway Bb", - :city "La Russell", - :state-abbrev "MO", - :zip "64848"} - {:lat 36.780462, - :lon -120.065853, - :house-number "15274", - :street "West Shields Avenue", - :city "Kerman", - :state-abbrev "CA", - :zip "93630"} - {:lat 35.34071, - :lon -86.855531, - :house-number "2051", - :street "Edmondson Road", - :city "Cornersville", - :state-abbrev "TN", - :zip "37047"} - {:lat 48.6032364, - :lon -116.9725721, - :house-number "22", - :street "Snick Road", - :city "Priest River", - :state-abbrev "ID", - :zip "83856"} - {:lat 41.5309186, - :lon -78.083709, - :house-number "1073", - :street "Bailey Run Road", - :city "Austin", - :state-abbrev "PA", - :zip "16720"} - {:lat 46.2863851, - :lon -122.2715574, - :house-number "21000", - :street "Spirit Lake Highway", - :city "Toutle", - :state-abbrev "WA", - :zip "98649"} - {:lat 32.376953, - :lon -81.519071, - :house-number "5460", - :street "Old River Road South", - :city "Brooklet", - :state-abbrev "GA", - :zip "30415"} - {:lat 35.5070479, - :lon -89.37359339999999, - :house-number "79", - :street "North Of Highway", - :city "Stanton", - :state-abbrev "TN", - :zip "38069"} - {:lat 46.6409403, - :lon -102.8973443, - :house-number "5331", - :street "116th Avenue Southwest", - :city "New England", - :state-abbrev "ND", - :zip "58647"} - {:lat 39.6049775, - :lon -91.3183488, - :house-number "15584", - :street "Old 79", - :city "New London", - :state-abbrev "MO", - :zip "63459"} - {:lat 41.8804234, - :lon -76.3494723, - :house-number "3044", - :street "Robinson Road", - :city "Ulster", - :state-abbrev "PA", - :zip "18850"} - {:lat 45.193929, - :lon -91.1803149, - :house-number "24722", - :street "240th Avenue", - :city "Cornell", - :state-abbrev "WI", - :zip "54732"} - {:lat 32.8449836, - :lon -81.0811368, - :house-number "365", - :street "Hickory Hill Road", - :city "Varnville", - :state-abbrev "SC", - :zip "29944"} - {:lat 33.1325727, - :lon -89.1536935, - :house-number "219", - :street "Perry Road", - :city "Louisville", - :state-abbrev "MS", - :zip "39339"} - {:lat 37.192428, - :lon -92.53576609999999, - :house-number "3914", - :street "Missouri 5", - :city "Mansfield", - :state-abbrev "MO", - :zip "65704"} - {:lat 37.9059184, - :lon -94.5065307, - :house-number "14b", - :street "County Road H", - :city "Richards", - :state-abbrev "MO", - :zip "64778"} - {:lat 31.3797001, - :lon -110.9571969, - :house-number "2582-2640", - :street "North Al Harrison Road", - :city "Nogales", - :state-abbrev "AZ", - :zip "85621"} - {:lat 42.160778, - :lon -121.885837, - :house-number "11206", - :street "Ruger Road", - :city "Klamath Falls", - :state-abbrev "OR", - :zip "97601"} - {:lat 42.7741203, - :lon -91.2382421, - :house-number "22331", - :street "312th Street", - :city "Garber", - :state-abbrev "IA", - :zip "52048"} - {:lat 40.39649, - :lon -82.979333, - :house-number "4633", - :street "Steamtown Road", - :city "Ashley", - :state-abbrev "OH", - :zip "43003"} - {:lat 44.4337856, - :lon -95.3307342, - :house-number "26000-26670", - :street "County Road 7", - :city "Wabasso", - :state-abbrev "MN", - :zip "56293"} - {:lat 59.75461900000001, - :lon -154.8722999, - :house-number "9998", - :street "Iliamna Village Road", - :city "Iliamna", - :state-abbrev "AK", - :zip "99606"} - {:lat 37.2799922, - :lon -89.8329473, - :house-number "136", - :street "State Highway RA", - :city "Whitewater", - :state-abbrev "MO", - :zip "63785"} - {:lat 30.0020916, - :lon -94.2665475, - :house-number "5831", - :street "Moonglow Drive", - :city "Beaumont", - :state-abbrev "TX", - :zip "77713"} - {:lat 30.2060304, - :lon -99.0853849, - :house-number "1631", - :street "Otto Staudt Road", - :city "Fredericksburg", - :state-abbrev "TX", - :zip "78624"} - {:lat 36.1941456, - :lon -87.98984159999999, - :house-number "561", - :street "Little Sulphur Creek Road", - :city "Big Sandy", - :state-abbrev "TN", - :zip "38221"} - {:lat 41.3768809, - :lon -102.1067028, - :house-number "5275", - :street "Road 203", - :city "Lewellen", - :state-abbrev "NE", - :zip "69147"} - {:lat 55.294047, - :lon -160.6788618, - :house-number "100", - :street "Main Street", - :city "Sand Point", - :state-abbrev "AK", - :zip "99661"} - {:lat 37.8951407, - :lon -89.06417429999999, - :house-number "4500-4758", - :street "East Euclid Street", - :city "Royalton", - :state-abbrev "IL", - :zip "62983"} - {:lat 41.686414, - :lon -88.634023, - :house-number "3832", - :street "West Sandwich Road", - :city "Sandwich", - :state-abbrev "IL", - :zip "60548"} - {:lat 29.2769731, - :lon -95.60418879999999, - :house-number "22828-23086", - :street "County Road 25", - :city "Damon", - :state-abbrev "TX", - :zip "77430"} - {:lat 44.7650241, - :lon -99.0478074, - :house-number "35700-35792", - :street "181st Street", - :city "Orient", - :state-abbrev "SD", - :zip "57467"} - {:lat 38.2021244, - :lon -105.6365082, - :house-number "761", - :street "County Road 191", - :city "Westcliffe", - :state-abbrev "CO", - :zip "81252"} - {:lat 34.7160477, - :lon -84.6346884, - :house-number "2641", - :street "Tails Creek Church Road", - :city "Ellijay", - :state-abbrev "GA", - :zip "30540"} - {:lat 43.6342315, - :lon -92.93457219999999, - :house-number "19443", - :street "560th Avenue", - :city "Austin", - :state-abbrev "MN", - :zip "55912"} - {:lat 33.0768658, - :lon -98.6260044, - :house-number "322", - :street "Medlan Chapel Road", - :city "Graham", - :state-abbrev "TX", - :zip "76450"} - {:lat 44.6974348, - :lon -109.6102857, - :house-number "561", - :street "Sunlight Road", - :city "Cody", - :state-abbrev "WY", - :zip "82414"} - {:lat 44.0728434, - :lon -95.7249308, - :house-number "1668-1698", - :street "156th Street", - :city "Slayton", - :state-abbrev "MN", - :zip "56172"} - {:lat 43.5026735, - :lon -84.5066867, - :house-number "3620", - :street "South Magrudder Road", - :city "St. Louis", - :state-abbrev "MI", - :zip "48880"} - {:lat 41.778891, - :lon -71.844071, - :house-number "191", - :street "Snake Meadow Road", - :city "Killingly", - :state-abbrev "CT", - :zip "06239"} - {:lat 45.42324980000001, - :lon -102.9816937, - :house-number "13627", - :street "Rabbit Creek Place", - :city "Reva", - :state-abbrev "SD", - :zip "57651"} - {:lat 41.6072879, - :lon -91.44762399999999, - :house-number "4960", - :street "U.S. 6", - :city "Iowa City", - :state-abbrev "IA", - :zip "52240"} - {:lat 43.8482247, - :lon -95.9080164, - :house-number "17229", - :street "1st Street", - :city "Chandler", - :state-abbrev "MN", - :zip "56122"} - {:lat 66.37241159999999, - :lon -150.4916213, - :house-number "15", - :street "North Slope Haul Road", - :city "Fairbanks", - :state-abbrev "AK", - :zip "99701"} - {:lat 40.47705, - :lon -82.26044399999999, - :house-number "17403", - :street "Kaylor Road", - :city "Danville", - :state-abbrev "OH", - :zip "43014"} - {:lat 43.339598, - :lon -82.67549059999999, - :house-number "3507-3745", - :street "Marlette Road", - :city "Croswell", - :state-abbrev "MI", - :zip "48422"} - {:lat 45.9052449, - :lon -96.0412765, - :house-number "23248", - :street "County Highway 8", - :city "Elbow Lake", - :state-abbrev "MN", - :zip "56531"} - {:lat 30.7336771, - :lon -94.9438504, - :house-number "100", - :street "Sunshine Lane", - :city "Livingston", - :state-abbrev "TX", - :zip "77351"} - {:lat 34.070783, - :lon -79.210173, - :house-number "17791", - :street "Pee Dee Road North", - :city "Galivants Ferry", - :state-abbrev "SC", - :zip "29544"} - {:lat 37.530722, - :lon -84.34361249999999, - :house-number "1", - :street "Old Garrard Road", - :city "Berea", - :state-abbrev "KY", - :zip "40403"} - {:lat 45.0499174, - :lon -91.9176634, - :house-number "N10285", - :street "490th Street", - :city "Wheeler", - :state-abbrev "WI", - :zip "54772"} - {:lat 32.5545752, - :lon -110.9335244, - :house-number "35498", - :street "Arizona 77", - :city "Tucson", - :state-abbrev "AZ", - :zip "85739"} - {:lat 29.231494, - :lon -98.58674599999999, - :house-number "19333", - :street "Texas 16", - :city "Von Ormy", - :state-abbrev "TX", - :zip "78073"} - {:lat 31.7874786, - :lon -88.7970596, - :house-number "38", - :street "Gene Roberts Drive", - :city "Shubuta", - :state-abbrev "MS", - :zip "39360"} - {:lat 62.1421096, - :lon -149.9113851, - :house-number "37555", - :street "South Kaliak", - :city "Talkeetna", - :state-abbrev "AK", - :zip "99676"} - {:lat 40.232191, - :lon -83.1745059, - :house-number "14850", - :street "Smart-Cole Road", - :city "Ostrander", - :state-abbrev "OH", - :zip "43061"} - {:lat 34.716032, - :lon -82.469116, - :house-number "805", - :street "River Road", - :city "Piedmont", - :state-abbrev "SC", - :zip "29673"} - {:lat 43.6307309, - :lon -70.8311294, - :house-number "111", - :street "Poverty Pond Road", - :city "Shapleigh", - :state-abbrev "ME", - :zip "04076"} - {:lat 41.7371709, - :lon -75.31117100000002, - :house-number "1711", - :street "Great Bend Turnpike", - :city "Pleasant Mount", - :state-abbrev "PA", - :zip "18453"} - {:lat 37.01058099999999, - :lon -79.571281, - :house-number "1861", - :street "Jasmine Road", - :city "Sandy Level", - :state-abbrev "VA", - :zip "24161"} - {:lat 39.6546536, - :lon -75.89517219999999, - :house-number "361", - :street "Leeds Road", - :city "Elkton", - :state-abbrev "MD", - :zip "21921"} - {:lat 42.0185478, - :lon -85.5427941, - :house-number "21250", - :street "Dentler Drive", - :city "Three Rivers", - :state-abbrev "MI", - :zip "49093"} - {:lat 44.0018723, - :lon -96.04839899999999, - :house-number "1045", - :street "10th Avenue", - :city "Woodstock", - :state-abbrev "MN", - :zip "56186"} - {:lat 38.390851, - :lon -90.4221929, - :house-number "2656", - :street "Fox Run", - :city "Imperial", - :state-abbrev "MO", - :zip "63052"} - {:lat 34.442297, - :lon -80.412144, - :house-number "3338", - :street "McCaskill Road", - :city "Bethune", - :state-abbrev "SC", - :zip "29009"} - {:lat 37.63756310000001, - :lon -86.3101248, - :house-number "3839", - :street "Mook-Centerview Road", - :city "Hudson", - :state-abbrev "KY", - :zip "40145"} - {:lat 44.8002186, - :lon -91.1806963, - :house-number "17210", - :street "Scenic Drive", - :city "Fall Creek", - :state-abbrev "WI", - :zip "54742"} - {:lat 39.6528941, - :lon -83.3396202, - :house-number "7470", - :street "Railroad Street Northeast", - :city "Mount Sterling", - :state-abbrev "OH", - :zip "43143"} - {:lat 38.36489090000001, - :lon -98.67333769999999, - :house-number "550B", - :street "U.S. 56", - :city "Great Bend", - :state-abbrev "KS", - :zip "67530"} - {:lat 45.75853559999999, - :lon -94.4943314, - :house-number "18633", - :street "440th Street", - :city "Holdingford", - :state-abbrev "MN", - :zip "56340"} - {:lat 32.173593, - :lon -98.95164749999999, - :house-number "402", - :street "County Road 292", - :city "Rising Star", - :state-abbrev "TX", - :zip "76471"} - {:lat 42.182373, - :lon -75.84169299999999, - :house-number "37", - :street "Country Knoll Drive", - :city "Binghamton", - :state-abbrev "NY", - :zip "13901"} - {:lat 34.4361918, - :lon -83.2612249, - :house-number "15285", - :street "Georgia 106", - :city "Carnesville", - :state-abbrev "GA", - :zip "30521"} - {:lat 43.8940625, - :lon -103.1231529, - :house-number "24217", - :street "Alihon Lane", - :city "Hermosa", - :state-abbrev "SD", - :zip "57744"} - {:lat 40.7652004, - :lon -86.8898238, - :house-number "488-998", - :street "West 100 North", - :city "Reynolds", - :state-abbrev "IN", - :zip "47980"} - {:lat 36.7088456, - :lon -79.67940689999999, - :house-number "463", - :street "Nowhere Road", - :city "Axton", - :state-abbrev "VA", - :zip "24054"} - {:lat 39.2996331, - :lon -89.8230667, - :house-number "20684", - :street "Claremont Road", - :city "Carlinville", - :state-abbrev "IL", - :zip "62626"} - {:lat 41.4699166, - :lon -96.3151407, - :house-number "6453", - :street "County Road 15", - :city "Arlington", - :state-abbrev "NE", - :zip "68002"} - {:lat 42.8194108, - :lon -74.4285355, - :house-number "1582", - :street "New York 162", - :city "Sprakers", - :state-abbrev "NY", - :zip "12166"} - {:lat 44.0269598, - :lon -104.7325072, - :house-number "818", - :street "State Highway 116 South", - :city "Upton", - :state-abbrev "WY", - :zip "82730"} - {:lat 33.4951893, - :lon -93.9307702, - :house-number "9655", - :street "Old Post Road", - :city "Texarkana", - :state-abbrev "AR", - :zip "71854"} - {:lat 43.3445718, - :lon -82.8238781, - :house-number "208", - :street "Morris Road", - :city "Sandusky", - :state-abbrev "MI", - :zip "48471"} - {:lat 40.0203256, - :lon -97.8166682, - :house-number "224", - :street "Road 4900", - :city "Hardy", - :state-abbrev "NE", - :zip "68943"} - {:lat 35.3401668, - :lon -85.1263524, - :house-number "202", - :street "McCallie Ferry Road", - :city "Soddy-Daisy", - :state-abbrev "TN", - :zip "37379"} - {:lat 47.4284009, - :lon -97.1742982, - :house-number "80", - :street "158th Avenue Northeast", - :city "Hillsboro", - :state-abbrev "ND", - :zip "58045"} - {:lat 42.38416, - :lon -123.078391, - :house-number "2585", - :street "Galls Creek Road", - :city "Gold Hill", - :state-abbrev "OR", - :zip "97525"} - {:lat 38.716067, - :lon -83.340987, - :house-number "345", - :street "Johnson Run Road", - :city "Blue Creek", - :state-abbrev "OH", - :zip "45616"} - {:lat 31.049082, - :lon -87.716759, - :house-number "56503", - :street "Paul Road", - :city "Perdido", - :state-abbrev "AL", - :zip "36562"} - {:lat 40.511278, - :lon -89.624106, - :house-number "15874", - :street "Red Shale Hill Road", - :city "Pekin", - :state-abbrev "IL", - :zip "61554"} - {:lat 34.5311922, - :lon -102.5352594, - :house-number "2901-2999", - :street "Texas 86", - :city "Friona", - :state-abbrev "TX", - :zip "79035"} - {:lat 34.0789972, - :lon -87.161383, - :house-number "85", - :street "June Lane", - :city "Arley", - :state-abbrev "AL", - :zip "35541"} - {:lat 37.345752, - :lon -112.624213, - :house-number "1100", - :street "Lydia's Canyon Road", - :city "Glendale", - :state-abbrev "UT", - :zip "84729"} - {:lat 38.1811093, - :lon -96.713141, - :house-number "758", - :street "H Road", - :city "Cedar Point", - :state-abbrev "KS", - :zip "66843"} - {:lat 39.8391125, - :lon -95.5968961, - :house-number "2278", - :street "Horned Owl Road", - :city "Hiawatha", - :state-abbrev "KS", - :zip "66434"} - {:lat 47.8330014, - :lon -100.3183434, - :house-number "1220-1298", - :street "29th Street Northeast", - :city "Anamoose", - :state-abbrev "ND", - :zip "58710"} - {:lat 44.93645, - :lon -88.1927369, - :house-number "8669", - :street "Valley Line Road", - :city "Oconto Falls", - :state-abbrev "WI", - :zip "54154"} - {:lat 47.853634, - :lon -121.500764, - :house-number "15819", - :street "Index-Galena Road", - :city "Sultan", - :state-abbrev "WA", - :zip "98294"} - {:lat 39.573651, - :lon -76.264066, - :house-number "320", - :street "Priestford Road", - :city "Churchville", - :state-abbrev "MD", - :zip "21028"} - {:lat 35.074197, - :lon -81.86616099999999, - :house-number "170", - :street "State Road S-42-1958", - :city "Chesnee", - :state-abbrev "SC", - :zip "29323"} - {:lat 42.3412681, - :lon -93.0848007, - :house-number "24590-24998", - :street "V Avenue", - :city "Eldora", - :state-abbrev "IA", - :zip "50627"} - {:lat 37.4311908, - :lon -103.4459611, - :house-number "32997", - :street "County Road 193.5", - :city "Kim", - :state-abbrev "CO", - :zip "81049"} - {:lat 35.689965, - :lon -83.922624, - :house-number "621", - :street "Butler Mill Road", - :city "Maryville", - :state-abbrev "TN", - :zip "37803"} - {:lat 41.7821158, - :lon -92.6880092, - :house-number "3551", - :street "50th Street", - :city "Grinnell", - :state-abbrev "IA", - :zip "50112"} - {:lat 43.4605346, - :lon -85.2398777, - :house-number "7800", - :street "Schmeid Road", - :city "Lakeview", - :state-abbrev "MI", - :zip "48850"} - {:lat 40.1309832, - :lon -110.6794079, - :house-number "36449", - :street "Strawberry River Road", - :city "Duchesne", - :state-abbrev "UT", - :zip "84021"} - {:lat 47.9993618, - :lon -102.9765214, - :house-number "11168", - :street "41st Street Northwest", - :city "Keene", - :state-abbrev "ND", - :zip "58847"} - {:lat 42.90755799999999, - :lon -89.06496299999999, - :house-number "676", - :street "Craig Road", - :city "Edgerton", - :state-abbrev "WI", - :zip "53534"} - {:lat 35.4347041, - :lon -102.2610425, - :house-number "101", - :street "U.S. 385", - :city "Boys Ranch", - :state-abbrev "TX", - :zip "79010"} - {:lat 37.036569, - :lon -119.428604, - :house-number "35213", - :street "Oak Springs Road", - :city "Tollhouse", - :state-abbrev "CA", - :zip "93667"} - {:lat 31.5683718, - :lon -98.7032416, - :house-number "321", - :street "County Road 549", - :city "Mullin", - :state-abbrev "TX", - :zip "76864"} - {:lat 40.2804507, - :lon -87.40699389999999, - :house-number "4500-4758", - :street "State Road 28", - :city "Williamsport", - :state-abbrev "IN", - :zip "47993"} - {:lat 35.926761, - :lon -82.64889210000001, - :house-number "741", - :street "Lewis Branch Road", - :city "Marshall", - :state-abbrev "NC", - :zip "28753"} - {:lat 61.52915400000001, - :lon -149.457747, - :house-number "4501", - :street "Well Site Road", - :city "Wasilla", - :state-abbrev "AK", - :zip "99654"} - {:lat 42.8486932, - :lon -92.8694371, - :house-number "13469-13599", - :street "Ivy Avenue", - :city "Greene", - :state-abbrev "IA", - :zip "50636"} - {:lat 43.8968648, - :lon -101.7609158, - :house-number "24206", - :street "Fairview Road", - :city "Philip", - :state-abbrev "SD", - :zip "57567"} - {:lat 35.525398, - :lon -119.463896, - :house-number "26323", - :street "Merced Avenue", - :city "Wasco", - :state-abbrev "CA", - :zip "93280"} - {:lat 43.8726352, - :lon -70.2875686, - :house-number "256", - :street "Yarmouth Road", - :city "Gray", - :state-abbrev "ME", - :zip "04039"} - {:lat 30.098465, - :lon -95.6346049, - :house-number "1720", - :street "Hicks Street", - :city "Tomball", - :state-abbrev "TX", - :zip "77375"} - {:lat 62.36383000000001, - :lon -150.3484531, - :house-number "5766", - :street "East Whispering Woods Avenue", - :city "Trapper Creek", - :state-abbrev "AK", - :zip "99688"} - {:lat 40.7840789, - :lon -79.7983422, - :house-number "101-199", - :street "Durango Lane", - :city "Cabot", - :state-abbrev "PA", - :zip "16023"} - {:lat 30.7506, - :lon -83.232, - :house-number "3680", - :street "Carroll Ulmer Road", - :city "Valdosta", - :state-abbrev "GA", - :zip "31601"} - {:lat 42.937593, - :lon -93.6410973, - :house-number "1747-1799", - :street "120th Street", - :city "Goodell", - :state-abbrev "IA", - :zip "50439"} - {:lat 43.1187216, - :lon -94.9136304, - :house-number "3600-3698", - :street "340th Avenue", - :city "Ruthven", - :state-abbrev "IA", - :zip "51358"} - {:lat 34.3883628, - :lon -89.4442464, - :house-number "369", - :street "Mississippi 30", - :city "Oxford", - :state-abbrev "MS", - :zip "38655"} - {:lat 48.8050359, - :lon -97.20627449999999, - :house-number "9601-9697", - :street "160th Avenue Northeast", - :city "Pembina", - :state-abbrev "ND", - :zip "58271"} - {:lat 41.3713447, - :lon -74.508512, - :house-number "180", - :street "County Road 22", - :city "Slate Hill", - :state-abbrev "NY", - :zip "10973"} - {:lat 32.3737973, - :lon -85.0684674, - :house-number "43", - :street "Downing Drive", - :city "Phenix City", - :state-abbrev "AL", - :zip "36869"} - {:lat 37.7498548, - :lon -78.25526839999999, - :house-number "179", - :street "Cloverdale Road", - :city "Bremo Bluff", - :state-abbrev "VA", - :zip "23022"} - {:lat 41.5099562, - :lon -92.4001047, - :house-number "1961", - :street "540th Avenue", - :city "Gibson", - :state-abbrev "IA", - :zip "50104"} - {:lat 44.3993806, - :lon -103.7008776, - :house-number "20716", - :street "Whitewood Creek Road", - :city "Sturgis", - :state-abbrev "SD", - :zip "57785"} - {:lat 36.4429932, - :lon -81.6218153, - :house-number "12996", - :street "Highway 88", - :city "Creston", - :state-abbrev "NC", - :zip "28615"} - {:lat 40.9406, - :lon -77.4245418, - :house-number "228", - :street "Back Road", - :city "Rebersburg", - :state-abbrev "PA", - :zip "16872"} - {:lat 33.7877413, - :lon -88.22801779999999, - :house-number "6350", - :street "County Lake Road", - :city "Sulligent", - :state-abbrev "AL", - :zip "35586"} - {:lat 55.294047, - :lon -160.6788618, - :house-number "100", - :street "Main Street", - :city "Sand Point", - :state-abbrev "AK", - :zip "99661"} - {:lat 43.4903212, - :lon -97.74377899999999, - :house-number "42264", - :street "269th Street", - :city "Alexandria", - :state-abbrev "SD", - :zip "57311"} - {:lat 38.731919, - :lon -75.501182, - :house-number "14329", - :street "Road 592", - :city "Bridgeville", - :state-abbrev "DE", - :zip "19933"} - {:lat 42.6917144, - :lon -100.7440356, - :house-number "5-31", - :street "290th Avenue", - :city "Valentine", - :state-abbrev "NE", - :zip "69201"} - {:lat 41.0493214, - :lon -81.5361166, - :house-number "101", - :street "West Emerling Avenue", - :city "Akron", - :state-abbrev "OH", - :zip "44301"} - {:lat 40.7486852, - :lon -106.6111981, - :house-number "4998", - :street "County Road 16", - :city "Walden", - :state-abbrev "CO", - :zip "80480"} - {:lat 38.65403999999999, - :lon -89.949741, - :house-number "520", - :street "Willow Bend Lane", - :city "O'Fallon", - :state-abbrev "IL", - :zip "62269"} - {:lat 48.169711, - :lon -96.74520530000001, - :house-number "32548", - :street "200th Street Northwest", - :city "Warren", - :state-abbrev "MN", - :zip "56762"} - {:lat 33.5299639, - :lon -104.8391361, - :house-number "292-434", - :street "Draper Road", - :city "Roswell", - :state-abbrev "NM", - :zip "88201"} - {:lat 40.2052092, - :lon -102.7168116, - :house-number "5001-5999", - :street "County Road 44", - :city "Yuma", - :state-abbrev "CO", - :zip "80759"} - {:lat 39.8454451, - :lon -75.2502988, - :house-number "825", - :street "Clonmell Road", - :city "Paulsboro", - :state-abbrev "NJ", - :zip "08066"} - {:lat 37.6251974, - :lon -103.3524315, - :house-number "43200", - :street "Colorado 109", - :city "Kim", - :state-abbrev "CO", - :zip "81049"} - {:lat 41.6501178, - :lon -90.1317763, - :house-number "4100-4116", - :street "Sand Road", - :city "Erie", - :state-abbrev "IL", - :zip "61250"} - {:lat 40.2049537, - :lon -80.39586109999999, - :house-number "1767", - :street "Brush Run Road", - :city "Avella", - :state-abbrev "PA", - :zip "15312"} - {:lat 40.390835, - :lon -87.9144398, - :house-number "36376", - :street "North 170 East Road", - :city "Rankin", - :state-abbrev "IL", - :zip "60960"} - {:lat 34.865119, - :lon -120.296285, - :house-number "4989", - :street "Foxen Canyon Road", - :city "Santa Maria", - :state-abbrev "CA", - :zip "93454"} - {:lat 36.1709076, - :lon -83.715249, - :house-number "285", - :street "Emory Road", - :city "Blaine", - :state-abbrev "TN", - :zip "37709"} - {:lat 32.5653807, - :lon -100.8001906, - :house-number "11498", - :street "County Road 4167", - :city "Hermleigh", - :state-abbrev "TX", - :zip "79526"} - {:lat 46.3313462, - :lon -90.4063331, - :house-number "9668-9674", - :street "Pleasant Lake Road", - :city "Upson", - :state-abbrev "WI", - :zip "54565"} - {:lat 64.846943, - :lon -148.2255821, - :house-number "5674", - :street "Old Ridge Trail", - :city "Fairbanks", - :state-abbrev "AK", - :zip "99709"} - {:lat 37.9969555, - :lon -122.9928238, - :house-number "27107-27539", - :street "Chimney Rock Road", - :city "Inverness", - :state-abbrev "CA", - :zip "94937"} - {:lat 40.2127077, - :lon -95.8461579, - :house-number "71669", - :street "639 Avenue", - :city "Humboldt", - :state-abbrev "NE", - :zip "68376"} - {:lat 46.4302493, - :lon -101.1323048, - :house-number "6746-6842", - :street "County Road 83", - :city "Flasher", - :state-abbrev "ND", - :zip "58535"} - {:lat 37.329562, - :lon -79.731115, - :house-number "1229", - :street "Shadow Mountain Lane", - :city "Blue Ridge", - :state-abbrev "VA", - :zip "24064"} - {:lat 33.0356797, - :lon -85.5411367, - :house-number "6203-6299", - :street "Chambers County 053", - :city "Wadley", - :state-abbrev "AL", - :zip "36276"} - {:lat 38.4266745, - :lon -121.652151, - :house-number "R3E", - :street "Camino Court", - :city "Dixon", - :state-abbrev "CA", - :zip "95620"} - {:lat 32.4891393, - :lon -82.79339829999999, - :house-number "730-800", - :street "Jw Warren Road", - :city "East Dublin", - :state-abbrev "GA", - :zip "31027"} - {:lat 41.3328225, - :lon -86.2217687, - :house-number "9701-10035", - :street "South Iris Road", - :city "Plymouth", - :state-abbrev "IN", - :zip "46563"} - {:lat 30.0216907, - :lon -90.186703, - :house-number "4501-4517", - :street "Alphonse Drive", - :city "Metairie", - :state-abbrev "LA", - :zip "70006"} - {:lat 33.737864, - :lon -95.5020739, - :house-number "3104", - :street "County Road 43270", - :city "Powderly", - :state-abbrev "TX", - :zip "75473"} - {:lat 33.474077, - :lon -83.04224800000001, - :house-number "4511", - :street "White Plains Road", - :city "White Plains", - :state-abbrev "GA", - :zip "30678"} - {:lat 42.9046499, - :lon -75.78098279999999, - :house-number "3701-3797", - :street "Erieville Road", - :city "Cazenovia", - :state-abbrev "NY", - :zip "13035"} - {:lat 42.0515048, - :lon -78.2052235, - :house-number "8299", - :street "Green Road", - :city "Bolivar", - :state-abbrev "NY", - :zip "14715"} - {:lat 36.4508851, - :lon -85.1597474, - :house-number "188", - :street "Victor Padgett", - :city "Monroe", - :state-abbrev "TN", - :zip "38573"} - {:lat 41.9092788, - :lon -89.05070889999999, - :house-number "1001", - :street "South Main Street", - :city "Rochelle", - :state-abbrev "IL", - :zip "61068"} - {:lat 34.89559560000001, - :lon -94.9734872, - :house-number "40523", - :street "Line Street", - :city "Le Flore", - :state-abbrev "OK", - :zip "74942"} - {:lat 47.366296, - :lon -108.518454, - :house-number "15182", - :street "Valentine Road", - :city "Roy", - :state-abbrev "MT", - :zip "59471"} - {:lat 43.0282796, - :lon -72.5981097, - :house-number "2-298", - :street "Windmill Hill Trail", - :city "Brookline", - :state-abbrev "VT", - :zip "05345"} - {:lat 42.7842145, - :lon -85.2515325, - :house-number "13981", - :street "Perry Road", - :city "Lake Odessa", - :state-abbrev "MI", - :zip "48849"} - {:lat 28.5522135, - :lon -81.35147409999999, - :house-number "2400-2418", - :street "East Colonial Drive", - :city "Orlando", - :state-abbrev "FL", - :zip "32803"} - {:lat 43.49627599999999, - :lon -84.054371, - :house-number "7130", - :street "Kochville Road", - :city "Freeland", - :state-abbrev "MI", - :zip "48623"} - {:lat 44.7162483, - :lon -108.1862576, - :house-number "2120", - :street "Ln 16 1/2", - :city "Lovell", - :state-abbrev "WY", - :zip "82431"} - {:lat 32.2029844, - :lon -89.7955089, - :house-number "177-183", - :street "Antioch-Shiloh Road", - :city "Pelahatchie", - :state-abbrev "MS", - :zip "39145"} - {:lat 43.7774483, - :lon -89.4570366, - :house-number "3942", - :street "8th Drive", - :city "Montello", - :state-abbrev "WI", - :zip "53949"} - {:lat 46.6066045, - :lon -109.5645057, - :house-number "1102-1162", - :street "Judith Gap Road", - :city "Judith Gap", - :state-abbrev "MT", - :zip "59453"} - {:lat 32.8635063, - :lon -84.8623672, - :house-number "629", - :street "Chipley Street", - :city "Pine Mountain", - :state-abbrev "GA", - :zip "31822"} - {:lat 30.1279774, - :lon -98.483712, - :house-number "2631", - :street "North US Highway 281", - :city "Blanco", - :state-abbrev "TX", - :zip "78606"} - {:lat 37.5581635, - :lon -89.535821, - :house-number "5485", - :street "County Road 535", - :city "Jackson", - :state-abbrev "MO", - :zip "63755"} - {:lat 35.9969869, - :lon -81.6819901, - :house-number "5895", - :street "North Carolina 90", - :city "Collettsville", - :state-abbrev "NC", - :zip "28611"} - {:lat 40.7380401, - :lon -83.28694360000001, - :house-number "8874-9380", - :street "Ohio 294", - :city "Harpster", - :state-abbrev "OH", - :zip "43323"} - {:lat 36.184963, - :lon -121.197168, - :house-number "43521", - :street "VÃa Canada", - :city "King City", - :state-abbrev "CA", - :zip "93930"} - {:lat 45.3941852, - :lon -90.93547889999999, - :house-number "2811", - :street "Martin Road", - :city "Sheldon", - :state-abbrev "WI", - :zip "54766"} - {:lat 41.0167821, - :lon -97.8283055, - :house-number "2406", - :street "East 23 Road", - :city "Hampton", - :state-abbrev "NE", - :zip "68843"} - {:lat 36.2943794, - :lon -84.09738039999999, - :house-number "301", - :street "Fox Lake Lane", - :city "LaFollette", - :state-abbrev "TN", - :zip "37766"} - {:lat 47.55104499999999, - :lon -106.080787, - :house-number "2913", - :street "Horse Creek Road", - :city "Circle", - :state-abbrev "MT", - :zip "59215"} - {:lat 39.268984, - :lon -77.19474199999999, - :house-number "9505", - :street "Meadow Ridge Lane", - :city "Laytonsville", - :state-abbrev "MD", - :zip "20882"} - {:lat 33.6957652, - :lon -93.00202499999999, - :house-number "274", - :street "Knight Road", - :city "Chidester", - :state-abbrev "AR", - :zip "71726"} - {:lat 32.8553266, - :lon -88.96096179999999, - :house-number "10041", - :street "Road 785", - :city "Philadelphia", - :state-abbrev "MS", - :zip "39350"} - {:lat 36.7202659, - :lon -78.5598595, - :house-number "971", - :street "Clay Road", - :city "Skipwith", - :state-abbrev "VA", - :zip "23968"} - {:lat 34.78028, - :lon -77.626577, - :house-number "112", - :street "Batchelor Road", - :city "Richlands", - :state-abbrev "NC", - :zip "28574"} - {:lat 36.3934825, - :lon -106.4900081, - :house-number "23196", - :street "U.S. 84", - :city "Abiquiu", - :state-abbrev "NM", - :zip "87510"} - {:lat 42.8717651, - :lon -76.63044459999999, - :house-number "5115", - :street "Ridge Road", - :city "Union Springs", - :state-abbrev "NY", - :zip "13160"} - {:lat 46.798863, - :lon -112.179823, - :house-number "8717", - :street "Chevallier Drive", - :city "Helena", - :state-abbrev "MT", - :zip "59602"} - {:lat 40.842401, - :lon -85.727317, - :house-number "810", - :street "Tipton Street", - :city "Lagro", - :state-abbrev "IN", - :zip "46941"} - {:lat 47.4690299, - :lon -111.448239, - :house-number "251", - :street "Polish Road", - :city "Great Falls", - :state-abbrev "MT", - :zip "59404"} - {:lat 30.326049, - :lon -92.203801, - :house-number "1899", - :street "Higginbotham Highway", - :city "Church Point", - :state-abbrev "LA", - :zip "70525"} - {:lat 40.18612299999999, - :lon -79.67120899999999, - :house-number "221", - :street "Waltz Mill Road", - :city "Ruffs Dale", - :state-abbrev "PA", - :zip "15679"} - {:lat 44.39979109999999, - :lon -68.8100769, - :house-number "38", - :street "Back Shore Road", - :city "Castine", - :state-abbrev "ME", - :zip "04421"} - {:lat 38.6913194, - :lon -86.4017087, - :house-number "27", - :street "Noe's Chicken House Road", - :city "Orleans", - :state-abbrev "IN", - :zip "47452"} - {:lat 29.43706, - :lon -82.813959, - :house-number "7231", - :street "Northwest 30th Street", - :city "Chiefland", - :state-abbrev "FL", - :zip "32626"} - {:lat 48.6167295, - :lon -99.1286372, - :house-number "7059-7099", - :street "83rd Street Northeast", - :city "Egeland", - :state-abbrev "ND", - :zip "58331"} - {:lat 34.1911408, - :lon -89.1202776, - :house-number "2425", - :street "Oak Forest Road", - :city "Pontotoc", - :state-abbrev "MS", - :zip "38863"} - {:lat 54.8488896, - :lon -163.4073178, - :house-number "180", - :street "Unimak Drive", - :city "False Pass", - :state-abbrev "AK", - :zip "99583"} - {:lat 61.2668195, - :lon -145.2833742, - :house-number "95", - :street "Alaska 4", - :city "Copper Center", - :state-abbrev "AK", - :zip "99573"} - {:lat 39.16605149999999, - :lon -123.3810453, - :house-number "11001", - :street "Low Gap Road", - :city "Ukiah", - :state-abbrev "CA", - :zip "95482"} - {:lat 42.2818569, - :lon -93.7430547, - :house-number "2100-2174", - :street "350th Street", - :city "Stanhope", - :state-abbrev "IA", - :zip "50246"} - {:lat 40.1801095, - :lon -98.23249589999999, - :house-number "2730", - :street "Road North", - :city "Lawrence", - :state-abbrev "NE", - :zip "68957"} - {:lat 44.104079, - :lon -111.4923297, - :house-number "3321", - :street "Pleasant Hill View", - :city "Ashton", - :state-abbrev "ID", - :zip "83420"} - {:lat 38.5209723, - :lon -106.6766573, - :house-number "2594-3538", - :street "County Road 76", - :city "Parlin", - :state-abbrev "CO", - :zip "81239"} - {:lat 34.25811789999999, - :lon -84.42085209999999, - :house-number "5683", - :street "Jay Green Road", - :city "Canton", - :state-abbrev "GA", - :zip "30114"} - {:lat 44.895405, - :lon -85.01131230000001, - :house-number "4571-4599", - :street "Musser Road", - :city "Mancelona", - :state-abbrev "MI", - :zip "49659"} - {:lat 34.8236058, - :lon -91.8405691, - :house-number "184", - :street "Lilly Lane", - :city "Lonoke", - :state-abbrev "AR", - :zip "72086"} - {:lat 28.6705901, - :lon -82.61898, - :house-number "9234-9256", - :street "Zebrafinch Avenue", - :city "Brooksville", - :state-abbrev "FL", - :zip "34614"} - {:lat 43.6585004, - :lon -98.13524149999999, - :house-number "40300-40398", - :street "257th Street", - :city "Mitchell", - :state-abbrev "SD", - :zip "57301"} - {:lat 41.711713, - :lon -79.9304678, - :house-number "31002", - :street "Johnson Road", - :city "Townville", - :state-abbrev "PA", - :zip "16360"} - {:lat 46.5652693, - :lon -96.1857107, - :house-number "14787", - :street "Minnesota 108", - :city "Pelican Rapids", - :state-abbrev "MN", - :zip "56572"} - {:lat 62.1402087, - :lon -149.9117934, - :house-number "37661", - :street "South Kaliak", - :city "Talkeetna", - :state-abbrev "AK", - :zip "99676"} - {:lat 41.4293833, - :lon -91.1865965, - :house-number "1901-1973", - :street "215th Street", - :city "Muscatine", - :state-abbrev "IA", - :zip "52761"} - {:lat 35.411868, - :lon -79.52678, - :house-number "1499", - :street "Plank Road", - :city "Robbins", - :state-abbrev "NC", - :zip "27325"} - {:lat 35.7831615, - :lon -91.6198103, - :house-number "225", - :street "Miller Creek Road", - :city "Batesville", - :state-abbrev "AR", - :zip "72501"} - {:lat 42.1059992, - :lon -71.71592009999999, - :house-number "171", - :street "Jones Road", - :city "Sutton", - :state-abbrev "MA", - :zip "01590"} - {:lat 38.643258, - :lon -76.558375, - :house-number "2540", - :street "Sharon Court", - :city "Sunderland", - :state-abbrev "MD", - :zip "20689"} - {:lat 42.607759, - :lon -76.354182, - :house-number "370", - :street "Clark Street Extended", - :city "Groton", - :state-abbrev "NY", - :zip "13073"} - {:lat 43.4861087, - :lon -96.9791156, - :house-number "46115", - :street "269th Street", - :city "Chancellor", - :state-abbrev "SD", - :zip "57015"} - {:lat 29.8558423, - :lon -94.097933, - :house-number "7665", - :street "Texas 73", - :city "Beaumont", - :state-abbrev "TX", - :zip "77705"} - {:lat 66.92456100000001, - :lon -151.505773, - :house-number "101", - :street "South Hickel Highway", - :city "Bettles", - :state-abbrev "AK", - :zip "99726"} - {:lat 29.9929309, - :lon -93.1543835, - :house-number "205", - :street "Hebert Trailer Park Road", - :city "Lake Charles", - :state-abbrev "LA", - :zip "70607"} - {:lat 33.601174, - :lon -81.15536999999999, - :house-number "6192", - :street "South Carolina 394", - :city "North", - :state-abbrev "SC", - :zip "29112"} - {:lat 34.960715, - :lon -78.781319, - :house-number "4850", - :street "Gainey Road", - :city "Fayetteville", - :state-abbrev "NC", - :zip "28306"} - {:lat 40.758517, - :lon -124.049391, - :house-number "2927", - :street "Freshwater Road", - :city "Eureka", - :state-abbrev "CA", - :zip "95503"} - {:lat 37.288416, - :lon -108.003251, - :house-number "493", - :street "Perins Peak Lane", - :city "Durango", - :state-abbrev "CO", - :zip "81301"} - {:lat 34.212497, - :lon -84.606105, - :house-number "576", - :street "Fincher Road", - :city "Canton", - :state-abbrev "GA", - :zip "30114"} - {:lat 43.4571255, - :lon -105.4554265, - :house-number "1245", - :street "Jenne Trail Road", - :city "Douglas", - :state-abbrev "WY", - :zip "82633"} - {:lat 46.1527482, - :lon -84.1175427, - :house-number "11443", - :street "East Gogomain Road", - :city "Goetzville", - :state-abbrev "MI", - :zip "49736"} - {:lat 38.2322784, - :lon -89.6670206, - :house-number "745-813", - :street "County Highway 12", - :city "Coulterville", - :state-abbrev "IL", - :zip "62237"} - {:lat 29.7620941, - :lon -98.5691174, - :house-number "31942-31950", - :street "Oak Ridge Parkway", - :city "Bulverde", - :state-abbrev "TX", - :zip "78163"} - {:lat 36.7177719, - :lon -86.44161539999999, - :house-number "759", - :street "Henry Clay Smith Road", - :city "Franklin", - :state-abbrev "KY", - :zip "42134"} - {:lat 47.7602102, - :lon -98.58079459999999, - :house-number "9343", - :street "24th Street Northeast", - :city "Tolna", - :state-abbrev "ND", - :zip "58380"} - {:lat 36.3361469, - :lon -97.63530899999999, - :house-number "16418", - :street "E0470 Road", - :city "Fairmont", - :state-abbrev "OK", - :zip "73736"} - {:lat 31.5882886, - :lon -83.4849861, - :house-number "196", - :street "Wyatt Way", - :city "Chula", - :state-abbrev "GA", - :zip "31733"} - {:lat 41.1434785, - :lon -95.15353309999999, - :house-number "1106", - :street "L Avenue", - :city "Elliott", - :state-abbrev "IA", - :zip "51532"} - {:lat 36.9492281, - :lon -90.1344712, - :house-number "7904", - :street "State Highway PP", - :city "Puxico", - :state-abbrev "MO", - :zip "63960"} - {:lat 33.999475, - :lon -90.88267689999999, - :house-number "7882", - :street "Mississippi 1", - :city "Duncan", - :state-abbrev "MS", - :zip "38740"} - {:lat 36.4568888, - :lon -114.8473813, - :house-number "17283", - :street "North Las Vegas Boulevard", - :city "Moapa", - :state-abbrev "NV", - :zip "89025"} - {:lat 27.331667, - :lon -82.08469099999999, - :house-number "11500", - :street "Curtis Road", - :city "Myakka City", - :state-abbrev "FL", - :zip "34251"} - {:lat 38.0299536, - :lon -100.3918896, - :house-number "26701-27699", - :street "East Oyler Road", - :city "Ingalls", - :state-abbrev "KS", - :zip "67853"} - {:lat 42.5574159, - :lon -89.5572457, - :house-number "3963", - :street "Carter Road", - :city "Juda", - :state-abbrev "WI", - :zip "53550"} - {:lat 30.669218, - :lon -89.077568, - :house-number "14262", - :street "Martha Redmond Road", - :city "Saucier", - :state-abbrev "MS", - :zip "39574"} - {:lat 37.8729318, - :lon -109.1608253, - :house-number "450", - :street "North Old Highway", - :city "Monticello", - :state-abbrev "UT", - :zip "84535"} - {:lat 39.929032, - :lon -109.381367, - :house-number "36168", - :street "South Archy Draw", - :city "Vernal", - :state-abbrev "UT", - :zip "84078"} - {:lat 37.5085713, - :lon -105.005597, - :house-number "200-298", - :street "Locust Street", - :city "La Veta", - :state-abbrev "CO", - :zip "81055"} - {:lat 34.5488649, - :lon -82.6195903, - :house-number "1101-1121", - :street "Harriett Circle", - :city "Anderson", - :state-abbrev "SC", - :zip "29621"} - {:lat 36.719095, - :lon -91.6816223, - :house-number "8880", - :street "County Road 9850", - :city "West Plains", - :state-abbrev "MO", - :zip "65775"} - {:lat 41.9937909, - :lon -94.49720429999999, - :house-number "701-799", - :street "250th Street", - :city "Scranton", - :state-abbrev "IA", - :zip "51462"} - {:lat 46.12154899999999, - :lon -111.597967, - :house-number "487", - :street "Lone Mountain Road", - :city "Toston", - :state-abbrev "MT", - :zip "59643"} - {:lat 37.720286, - :lon -121.794001, - :house-number "1133", - :street "Hartman Road", - :city "Livermore", - :state-abbrev "CA", - :zip "94551"} - {:lat 47.0110008, - :lon -100.3562825, - :house-number "26859-27499", - :street "188th Avenue Northeast", - :city "Wing", - :state-abbrev "ND", - :zip "58494"} - {:lat 45.6197676, - :lon -93.2683454, - :house-number "35996", - :street "University Avenue Northeast", - :city "Cambridge", - :state-abbrev "MN", - :zip "55008"} - {:lat 46.1212212, - :lon -93.5872875, - :house-number "4001-20827", - :street "85th Avenue", - :city "Onamia", - :state-abbrev "MN", - :zip "56359"} - {:lat 39.23785, - :lon -80.020365, - :house-number "651", - :street "Cole Road", - :city "Philippi", - :state-abbrev "WV", - :zip "26416"} - {:lat 46.5980332, - :lon -98.2902184, - :house-number "10168", - :street "56th Street Southeast", - :city "Marion", - :state-abbrev "ND", - :zip "58466"} - {:lat 37.0512191, - :lon -94.8314747, - :house-number "8", - :street "U.S. 69", - :city "Baxter Springs", - :state-abbrev "KS", - :zip "66713"} - {:lat 32.335616, - :lon -86.308317, - :house-number "3860", - :street "South Court Street", - :city "Montgomery", - :state-abbrev "AL", - :zip "36105"} - {:lat 39.733923, - :lon -84.050685, - :house-number "3119", - :street "Windmill Drive", - :city "Dayton", - :state-abbrev "OH", - :zip "45432"} - {:lat 30.9973976, - :lon -81.90707379999999, - :house-number "4328", - :street "3r Fish Camp Road", - :city "White Oak", - :state-abbrev "GA", - :zip "31568"} - {:lat 38.963328, - :lon -78.935407, - :house-number "9918", - :street "Howards Lick Road", - :city "Mathias", - :state-abbrev "WV", - :zip "26812"} - {:lat 26.1594226, - :lon -98.03753689999999, - :house-number "2607", - :street "Yanez Street", - :city "Donna", - :state-abbrev "TX", - :zip "78537"} - {:lat 33.9896453, - :lon -96.7604564, - :house-number "11410", - :street "Enos Road", - :city "Kingston", - :state-abbrev "OK", - :zip "73439"} - {:lat 44.1881709, - :lon -74.432176, - :house-number "6", - :street "Lake Simond Road", - :city "Tupper Lake", - :state-abbrev "NY", - :zip "12986"} - {:lat 40.8811136, - :lon -102.4120101, - :house-number "12000-12998", - :street "County Road 20", - :city "Ovid", - :state-abbrev "CO", - :zip "80744"} - {:lat 47.0568583, - :lon -109.1692646, - :house-number "95162", - :street "U.S. 87", - :city "Lewistown", - :state-abbrev "MT", - :zip "59457"} - {:lat 32.8088943, - :lon -85.4291454, - :house-number "7230-8282", - :street "Chambers County 173", - :city "La Fayette", - :state-abbrev "AL", - :zip "36862"} - {:lat 48.2667954, - :lon -112.1198458, - :house-number "6854", - :street "Messenger Road", - :city "Valier", - :state-abbrev "MT", - :zip "59486"} - {:lat 40.4162433, - :lon -95.01358599999999, - :house-number "21115", - :street "State Highway Ab", - :city "Burlington Junction", - :state-abbrev "MO", - :zip "64428"} - {:lat 44.12387349999999, - :lon -116.3023244, - :house-number "19200", - :street "Sweet Ola Highway", - :city "Ola", - :state-abbrev "ID", - :zip "83657"} - {:lat 33.664848, - :lon -87.000664, - :house-number "5150", - :street "Highway 78 East", - :city "Graysville", - :state-abbrev "AL", - :zip "35073"} - {:lat 41.898599, - :lon -79.740098, - :house-number "1922", - :street "South Main Street", - :city "Corry", - :state-abbrev "PA", - :zip "16407"} - {:lat 34.6712327, - :lon -83.49263409999999, - :house-number "1501", - :street "Frank Lovell Road", - :city "Clarkesville", - :state-abbrev "GA", - :zip "30523"} - {:lat 39.699357, - :lon -105.412938, - :house-number "263", - :street "Barrows Ranch Road", - :city "Evergreen", - :state-abbrev "CO", - :zip "80439"} - {:lat 36.4451252, - :lon -85.31148429999999, - :house-number "164", - :street "Frogtown Estate", - :city "Livingston", - :state-abbrev "TN", - :zip "38570"} - {:lat 42.692484, - :lon -82.78239789999999, - :house-number "32792-32798", - :street "Greenwood Drive", - :city "New Baltimore", - :state-abbrev "MI", - :zip "48047"} - {:lat 38.1628115, - :lon -99.1111131, - :house-number "1252", - :street "U.S. 56", - :city "Larned", - :state-abbrev "KS", - :zip "67550"} - {:lat 44.7408289, - :lon -121.1276098, - :house-number "7269", - :street "North Adams Drive", - :city "Madras", - :state-abbrev "OR", - :zip "97741"} - {:lat 27.013422, - :lon -80.423394, - :house-number "12002", - :street "Southwest Kanner Highway", - :city "Indiantown", - :state-abbrev "FL", - :zip "34956"} - {:lat 40.58038200000001, - :lon -95.8412306, - :house-number "6590", - :street "O Road", - :city "Nebraska City", - :state-abbrev "NE", - :zip "68410"} - {:lat 36.50025979999999, - :lon -90.32461889999999, - :house-number "520-550", - :street "Clay County Road 320", - :city "Qulin", - :state-abbrev "MO", - :zip "63961"} - {:lat 39.0543141, - :lon -88.27940439999999, - :house-number "4001-5147", - :street "East 1400th Avenue", - :city "Wheeler", - :state-abbrev "IL", - :zip "62479"} - {:lat 38.82139799999999, - :lon -75.54764899999999, - :house-number "11656", - :street "Utica Road", - :city "Greenwood", - :state-abbrev "DE", - :zip "19950"} - {:lat 38.814901, - :lon -121.135696, - :house-number "3874", - :street "El Monte Drive", - :city "Loomis", - :state-abbrev "CA", - :zip "95650"} - {:lat 35.838619, - :lon -77.163956, - :house-number "203", - :street "East Barnhill Street", - :city "Williamston", - :state-abbrev "NC", - :zip "27892"} - {:lat 31.3768988, - :lon -96.9819914, - :house-number "653", - :street "County Road 105", - :city "Riesel", - :state-abbrev "TX", - :zip "76682"} - {:lat 41.174038, - :lon -87.22173599999999, - :house-number "9190", - :street "West 1100 North", - :city "De Motte", - :state-abbrev "IN", - :zip "46310"} - {:lat 32.446336, - :lon -95.38271999999999, - :house-number "12197", - :street "Cross Fence Trail", - :city "Tyler", - :state-abbrev "TX", - :zip "75706"} - {:lat 34.0320311, - :lon -83.1427077, - :house-number "64", - :street "Goose Pond Road", - :city "Comer", - :state-abbrev "GA", - :zip "30629"} - {:lat 35.8515632, - :lon -78.702987, - :house-number "5004-5008", - :street "Picardy Place", - :city "Raleigh", - :state-abbrev "NC", - :zip "27612"} - {:lat 38.3571942, - :lon -78.4127535, - :house-number "1215", - :street "Kinderhook Road", - :city "Madison", - :state-abbrev "VA", - :zip "22727"} - {:lat 61.2668195, - :lon -145.2833742, - :house-number "95", - :street "Alaska 4", - :city "Copper Center", - :state-abbrev "AK", - :zip "99573"} - {:lat 40.2482635, - :lon -87.25751629999999, - :house-number "3190-3398", - :street "County Road 30 East", - :city "Attica", - :state-abbrev "IN", - :zip "47918"} - {:lat 36.31394, - :lon -92.37312899999999, - :house-number "308", - :street "Pebblecreek Drive", - :city "Mountain Home", - :state-abbrev "AR", - :zip "72653"} - {:lat 34.0787283, - :lon -92.6510928, - :house-number "4466", - :street "Arkansas 9", - :city "Carthage", - :state-abbrev "AR", - :zip "71725"} - {:lat 34.9490126, - :lon -83.80166799999999, - :house-number "497", - :street "Old Ferguson Town Road", - :city "Young Harris", - :state-abbrev "GA", - :zip "30582"} - {:lat 41.3283563, - :lon -84.8139396, - :house-number "7934", - :street "County Road 56", - :city "Saint Joe", - :state-abbrev "IN", - :zip "46785"} - {:lat 38.726831, - :lon -78.6577884, - :house-number "1026-1324", - :street "Industrial Park", - :city "Mount Jackson", - :state-abbrev "VA", - :zip "22842"} - {:lat 36.8177783, - :lon -93.8447328, - :house-number "7706", - :street "Farm Road 1120", - :city "Verona", - :state-abbrev "MO", - :zip "65769"} - {:lat 34.886794, - :lon -76.79080499999999, - :house-number "1155", - :street "Temples Point Road", - :city "Havelock", - :state-abbrev "NC", - :zip "28532"} - {:lat 40.6505745, - :lon -81.4352846, - :house-number "11439-11447", - :street "Glenpark Drive Northeast", - :city "Bolivar", - :state-abbrev "OH", - :zip "44612"} - {:lat 44.0510283, - :lon -86.0815351, - :house-number "7800-8094", - :street "Burley", - :city "Township of Branch", - :state-abbrev "MI", - :zip "49402"} - {:lat 41.717989, - :lon -72.30425199999999, - :house-number "41", - :street "Laurel Lane", - :city "Columbia", - :state-abbrev "CT", - :zip "06237"} - {:lat 48.4679556, - :lon -116.4196161, - :house-number "3312", - :street "National Forest Development Road 215", - :city "Sandpoint", - :state-abbrev "ID", - :zip "83864"} - {:lat 32.5672093, - :lon -95.7323965, - :house-number "1610", - :street "Vz County Road 1313", - :city "Canton", - :state-abbrev "TX", - :zip "75103"} - {:lat 43.223702, - :lon -77.527873, - :house-number "194", - :street "Colonial Drive", - :city "Webster", - :state-abbrev "NY", - :zip "14580"} - {:lat 32.8239852, - :lon -82.9221041, - :house-number "1206", - :street "Georgia 272", - :city "Tennille", - :state-abbrev "GA", - :zip "31089"} - {:lat 33.295844, - :lon -88.440877, - :house-number "14463", - :street "Old Macon Road", - :city "Columbus", - :state-abbrev "MS", - :zip "39701"} - {:lat 34.9712724, - :lon -78.9891936, - :house-number "3701", - :street "Applegate Road", - :city "Hope Mills", - :state-abbrev "NC", - :zip "28348"} - {:lat 35.463022, - :lon -77.113389, - :house-number "304", - :street "Warren Avenue", - :city "Chocowinity", - :state-abbrev "NC", - :zip "27817"} - {:lat 36.507964, - :lon -91.3047636, - :house-number "22828", - :street "Highway V", - :city "Myrtle", - :state-abbrev "MO", - :zip "65778"} - {:lat 45.9087868, - :lon -113.26501, - :house-number "2499", - :street "Fish Trap Road", - :city "Wise River", - :state-abbrev "MT", - :zip "59762"} - {:lat 37.82054300000001, - :lon -95.6682008, - :house-number "1821", - :street "70th Road", - :city "Yates Center", - :state-abbrev "KS", - :zip "66783"} - {:lat 38.7003089, - :lon -91.29538629999999, - :house-number "5290", - :street "Missouri 94", - :city "Marthasville", - :state-abbrev "MO", - :zip "63357"} - {:lat 41.1057731, - :lon -81.564117, - :house-number "1567-1573", - :street "West Exchange Street", - :city "Akron", - :state-abbrev "OH", - :zip "44313"} - {:lat 38.6242147, - :lon -95.4527918, - :house-number "3272-3348", - :street "Colorado Road", - :city "Pomona", - :state-abbrev "KS", - :zip "66076"} - {:lat 41.3111128, - :lon -94.4145582, - :house-number "2315-2347", - :street "Pinewood Avenue", - :city "Greenfield", - :state-abbrev "IA", - :zip "50849"} - {:lat 42.2345635, - :lon -76.83706459999999, - :house-number "4898", - :street "Clair Road", - :city "Millport", - :state-abbrev "NY", - :zip "14864"} - {:lat 45.7455717, - :lon -87.1809976, - :house-number "3701-3841", - :street "14.5 Road", - :city "Escanaba", - :state-abbrev "MI", - :zip "49829"} - {:lat 47.500227, - :lon -118.1411565, - :house-number "16423", - :street "Star Barn Road North", - :city "Davenport", - :state-abbrev "WA", - :zip "99122"} - {:lat 40.5177699, - :lon -90.4266696, - :house-number "21501", - :street "North Point Pleasant Road", - :city "Marietta", - :state-abbrev "IL", - :zip "61459"} - {:lat 36.034604, - :lon -86.844132, - :house-number "311", - :street "Deerwood Lane", - :city "Brentwood", - :state-abbrev "TN", - :zip "37027"} - {:lat 41.9659007, - :lon -92.8931427, - :house-number "509", - :street "Roberts Terrace", - :city "Marshalltown", - :state-abbrev "IA", - :zip "50158"} - {:lat 42.9233932, - :lon -95.4129181, - :house-number "6957-6999", - :street "Highway 10 Boulevard", - :city "Sutherland", - :state-abbrev "IA", - :zip "51058"} - {:lat 34.7953365, - :lon -92.7700229, - :house-number "24523-25321", - :street "Arkansas 9", - :city "Paron", - :state-abbrev "AR", - :zip "72122"} - {:lat 35.653733, - :lon -85.979012, - :house-number "11481", - :street "Shelbyville Road", - :city "Morrison", - :state-abbrev "TN", - :zip "37357"} - {:lat 45.92924499999999, - :lon -108.3549564, - :house-number "4851-5197", - :street "Yeoman Road", - :city "Shepherd", - :state-abbrev "MT", - :zip "59079"} - {:lat 36.1896105, - :lon -102.327912, - :house-number "13009", - :street "Rock Hill Road", - :city "Dalhart", - :state-abbrev "TX", - :zip "79022"} - {:lat 38.41670149999999, - :lon -122.753963, - :house-number "1426-1498", - :street "Corporate Center Parkway", - :city "Santa Rosa", - :state-abbrev "CA", - :zip "95407"} - {:lat 44.222597, - :lon -89.0943339, - :house-number "N6698", - :street "East Long Lake Road", - :city "Wild Rose", - :state-abbrev "WI", - :zip "54984"} - {:lat 42.0922467, - :lon -85.26004549999999, - :house-number "1821", - :street "V Drive South", - :city "Athens", - :state-abbrev "MI", - :zip "49011"} - {:lat 33.5235064, - :lon -94.7968594, - :house-number "765", - :street "County Road 4426", - :city "Avery", - :state-abbrev "TX", - :zip "75554"} - {:lat 42.026117, - :lon -74.804464, - :house-number "237", - :street "Mary Smith Hill Road", - :city "Livingston Manor", - :state-abbrev "NY", - :zip "12758"} - {:lat 47.7415303, - :lon -112.3168472, - :house-number "300-398", - :street "Pishkun Road", - :city "Choteau", - :state-abbrev "MT", - :zip "59422"} - {:lat 48.2485359, - :lon -104.6062252, - :house-number "5175", - :street "Road 1026", - :city "Froid", - :state-abbrev "MT", - :zip "59226"} - {:lat 45.4596787, - :lon -110.1984557, - :house-number "3319", - :street "Main Boulder Road", - :city "Mc Leod", - :state-abbrev "MT", - :zip "59052"} - {:lat 37.4279015, - :lon -77.75752279999999, - :house-number "18100", - :street "Duval Road", - :city "Moseley", - :state-abbrev "VA", - :zip "23120"} - {:lat 46.6669749, - :lon -92.0631631, - :house-number "98", - :street "Ostby Drive", - :city "Superior", - :state-abbrev "WI", - :zip "54880"} - {:lat 37.6065566, - :lon -98.73944519999999, - :house-number "20469-20731", - :street "South 1st Avenue", - :city "Pratt", - :state-abbrev "KS", - :zip "67124"} - {:lat 41.9584444, - :lon -95.5465569, - :house-number "2743", - :street "Dane Ridge Road", - :city "Dow City", - :state-abbrev "IA", - :zip "51528"} - {:lat 36.3495959, - :lon -92.29217919999999, - :house-number "4398", - :street "Buzzard Roost Road", - :city "Mountain Home", - :state-abbrev "AR", - :zip "72653"} - {:lat 40.8955618, - :lon -102.408923, - :house-number "12508-12998", - :street "County Road 22", - :city "Ovid", - :state-abbrev "CO", - :zip "80744"} - {:lat 43.9300967, - :lon -88.9492695, - :house-number "N8279", - :street "Wisconsin 49", - :city "Berlin", - :state-abbrev "WI", - :zip "54923"} - {:lat 32.4174517, - :lon -87.53871079999999, - :house-number "22605", - :street "County Road 53", - :city "Uniontown", - :state-abbrev "AL", - :zip "36786"} - {:lat 34.099726, - :lon -98.68657929999999, - :house-number "573", - :street "Williamson Road", - :city "Burkburnett", - :state-abbrev "TX", - :zip "76354"} - {:lat 46.4754497, - :lon -110.0784601, - :house-number "21-585", - :street "Haymaker Road", - :city "Harlowton", - :state-abbrev "MT", - :zip "59036"} - {:lat 39.038336, - :lon -104.174353, - :house-number "34255", - :street "Harrisville Road", - :city "Calhan", - :state-abbrev "CO", - :zip "80808"} - {:lat 37.861802, - :lon -120.625913, - :house-number "13149", - :street "Tulloch Dam Road", - :city "Jamestown", - :state-abbrev "CA", - :zip "95327"} - {:lat 44.5040542, - :lon -85.5457574, - :house-number "1400", - :street "East 2 1/2 Road", - :city "Kingsley", - :state-abbrev "MI", - :zip "49649"} - {:lat 35.7394238, - :lon -93.6382065, - :house-number "718-798", - :street "County Road 5141", - :city "Pettigrew", - :state-abbrev "AR", - :zip "72752"} - {:lat 45.5285437, - :lon -88.3064481, - :house-number "N15535", - :street "Parkway Road", - :city "Athelstane", - :state-abbrev "WI", - :zip "54104"} - {:lat 44.4656923, - :lon -106.6800018, - :house-number "47", - :street "Belus Road", - :city "Buffalo", - :state-abbrev "WY", - :zip "82834"} - {:lat 34.2037273, - :lon -116.5911904, - :house-number "50951", - :street "Burns Canyon Road", - :city "Pioneertown", - :state-abbrev "CA", - :zip "92268"} - {:lat 31.7196718, - :lon -84.1848424, - :house-number "259", - :street "Highway 32 East", - :city "Leesburg", - :state-abbrev "GA", - :zip "31763"} - {:lat 36.360493, - :lon -77.22337, - :house-number "439", - :street "Baughan Road", - :city "Woodland", - :state-abbrev "NC", - :zip "27897"} - {:lat 42.1887029, - :lon -74.13561899999999, - :house-number "11", - :street "Quarry Road", - :city "Tannersville", - :state-abbrev "NY", - :zip "12485"} - {:lat 30.008587, - :lon -98.826402, - :house-number "107", - :street "Old Comfort Road", - :city "Fredericksburg", - :state-abbrev "TX", - :zip "78624"} - {:lat 34.3910982, - :lon -85.3040286, - :house-number "537", - :street "Silver Leaf Drive", - :city "Summerville", - :state-abbrev "GA", - :zip "30747"} - {:lat 34.59938940000001, - :lon -82.15108649999999, - :house-number "11238", - :street "South Carolina 101", - :city "Gray Court", - :state-abbrev "SC", - :zip "29645"} - {:lat 33.5782505, - :lon -97.78660839999999, - :house-number "183", - :street "Hopson Road", - :city "Bowie", - :state-abbrev "TX", - :zip "76230"} - {:lat 41.285628, - :lon -85.998631, - :house-number "3734", - :street "North 800 West", - :city "Warsaw", - :state-abbrev "IN", - :zip "46582"} - {:lat 46.368916, - :lon -91.984899, - :house-number "6085", - :street "South County Road A", - :city "Solon Springs", - :state-abbrev "WI", - :zip "54873"} - {:lat 65.080709, - :lon -148.034074, - :house-number "4916", - :street "Rossburg Road", - :city "Fairbanks", - :state-abbrev "AK", - :zip "99712"} - {:lat 44.7638897, - :lon -99.2930452, - :house-number "18100-18114", - :street "345th Avenue", - :city "Orient", - :state-abbrev "SD", - :zip "57467"} - {:lat 36.9155806, - :lon -92.9576551, - :house-number "6642-7862", - :street "Chadwick Road", - :city "Chadwick", - :state-abbrev "MO", - :zip "65629"} - {:lat 40.3416011, - :lon -102.3142967, - :house-number "52944-53578", - :street "County Road CC", - :city "Wray", - :state-abbrev "CO", - :zip "80758"} - {:lat 43.0951171, - :lon -88.9768565, - :house-number "8970", - :street "Michel Lane", - :city "Waterloo", - :state-abbrev "WI", - :zip "53594"} - {:lat 46.2973537, - :lon -115.961682, - :house-number "906", - :street "Hidden Valley Lane", - :city "Weippe", - :state-abbrev "ID", - :zip "83553"} - {:lat 40.17479489999999, - :lon -87.6572382, - :house-number "1304", - :street "Park Haven Court", - :city "Danville", - :state-abbrev "IL", - :zip "61832"} - {:lat 41.2536332, - :lon -88.5247739, - :house-number "5377-5733", - :street "County Road 2000 South", - :city "Verona", - :state-abbrev "IL", - :zip "60479"} - {:lat 32.7908147, - :lon -85.1437507, - :house-number "45", - :street "Middle Street", - :city "Valley", - :state-abbrev "AL", - :zip "36854"} - {:lat 39.41639809999999, - :lon -87.3481896, - :house-number "4401", - :street "Riley Road", - :city "Terre Haute", - :state-abbrev "IN", - :zip "47802"} - {:lat 38.72755, - :lon -75.94157899999999, - :house-number "4346", - :street "Jones Lane", - :city "Preston", - :state-abbrev "MD", - :zip "21655"} - {:lat 42.0223194, - :lon -118.6227513, - :house-number "13546", - :street "Fields-Denio Road", - :city "Fields", - :state-abbrev "OR", - :zip "97710"} - {:lat 46.7163023, - :lon -94.564637, - :house-number "5305", - :street "24th Street Southwest", - :city "Pine River", - :state-abbrev "MN", - :zip "56474"} - {:lat 40.2912243, - :lon -98.57218610000001, - :house-number "919", - :street "Nebraska 4", - :city "Bladen", - :state-abbrev "NE", - :zip "68928"} - {:lat 45.0784019, - :lon -90.1627199, - :house-number "2138", - :street "County Road F", - :city "Athens", - :state-abbrev "WI", - :zip "54411"} - {:lat 36.015878, - :lon -89.431719, - :house-number "2142", - :street "Samaria Bend Road", - :city "Dyersburg", - :state-abbrev "TN", - :zip "38024"} - {:lat 47.3138924, - :lon -100.7064244, - :house-number "45737-46099", - :street "52nd Street Northeast", - :city "Wilton", - :state-abbrev "ND", - :zip "58579"} - {:lat 43.1130022, - :lon -90.60294999999999, - :house-number "16701-16899", - :street "County Road T", - :city "Boscobel", - :state-abbrev "WI", - :zip "53805"} - {:lat 47.79393899999999, - :lon -111.6032917, - :house-number "1260", - :street "18th Lane Northeast", - :city "Power", - :state-abbrev "MT", - :zip "59468"} - {:lat 35.5422894, - :lon -83.069052, - :house-number "2-158", - :street "Simpson Lane", - :city "Maggie Valley", - :state-abbrev "NC", - :zip "28751"} - {:lat 39.7718983, - :lon -75.4900014, - :house-number "1506-1508", - :street "Woodsdale Road", - :city "Wilmington", - :state-abbrev "DE", - :zip "19809"} - {:lat 47.150309, - :lon -100.2381976, - :house-number "31351", - :street "340th Street Northeast", - :city "Wing", - :state-abbrev "ND", - :zip "58494"} - {:lat 32.1289155, - :lon -81.93196429999999, - :house-number "122", - :street "Deer Run Circle", - :city "Claxton", - :state-abbrev "GA", - :zip "30417"} - {:lat 41.0531684, - :lon -86.6946993, - :house-number "4785", - :street "Indiana 14", - :city "Winamac", - :state-abbrev "IN", - :zip "46996"} - {:lat 47.10962199999999, - :lon -96.933262, - :house-number "16845", - :street "21st Street Southeast", - :city "Argusville", - :state-abbrev "ND", - :zip "58005"} - {:lat 43.056554, - :lon -77.611637, - :house-number "3224", - :street "East Henrietta Road", - :city "Henrietta", - :state-abbrev "NY", - :zip "14467"} - {:lat 46.560284, - :lon -104.093908, - :house-number "115", - :street "Tatley Road", - :city "Baker", - :state-abbrev "MT", - :zip "59313"} - {:lat 41.47658800000001, - :lon -85.377605, - :house-number "2565", - :street "East 850 North", - :city "Rome City", - :state-abbrev "IN", - :zip "46784"} - {:lat 31.1026404, - :lon -92.180238, - :house-number "1247", - :street "Highway 114", - :city "Hessmer", - :state-abbrev "LA", - :zip "71341"} - {:lat 43.2555386, - :lon -71.0671672, - :house-number "605", - :street "Berry River Road", - :city "Barrington", - :state-abbrev "NH", - :zip "03825"} - {:lat 29.79807, - :lon -99.192702, - :house-number "2247", - :street "Farm to Market 2828", - :city "Bandera", - :state-abbrev "TX", - :zip "78003"} - {:lat 43.7009933, - :lon -95.61413950000001, - :house-number "20161", - :street "Paul Avenue", - :city "Worthington", - :state-abbrev "MN", - :zip "56187"} - {:lat 35.3964691, - :lon -98.8329761, - :house-number "23317", - :street "East 1120 Road", - :city "Corn", - :state-abbrev "OK", - :zip "73024"} - {:lat 43.2321938, - :lon -96.1972263, - :house-number "2800-2898", - :street "Grant Avenue", - :city "Hull", - :state-abbrev "IA", - :zip "51239"} - {:lat 41.9509765, - :lon -90.4607997, - :house-number "1529-1603", - :street "330th Avenue", - :city "Charlotte", - :state-abbrev "IA", - :zip "52731"} - {:lat 43.1131538, - :lon -115.7902421, - :house-number "5444-6392", - :street "Airbase Road", - :city "Mountain Home", - :state-abbrev "ID", - :zip "83647"} - {:lat 43.6019566, - :lon -95.4120336, - :house-number "33530-33532", - :street "770th Street", - :city "Round Lake", - :state-abbrev "MN", - :zip "56167"} - {:lat 40.3189104, - :lon -84.36594269999999, - :house-number "3696", - :street "Basinburg Road", - :city "Fort Loramie", - :state-abbrev "OH", - :zip "45845"} - {:lat 33.2467157, - :lon -89.22798739999999, - :house-number "11022", - :street "Penderville Road", - :city "Weir", - :state-abbrev "MS", - :zip "39772"} - {:lat 43.745157, - :lon -70.460449, - :house-number "7", - :street "Homestead Lane", - :city "Gorham", - :state-abbrev "ME", - :zip "04038"} - {:lat 41.2099688, - :lon -90.9370234, - :house-number "901-999", - :street "Bluff Road", - :city "Joy", - :state-abbrev "IL", - :zip "61260"} - {:lat 46.31263879999999, - :lon -95.60988979999999, - :house-number "24100-24998", - :street "423rd Avenue", - :city "Battle Lake", - :state-abbrev "MN", - :zip "56515"} - {:lat 44.70675540000001, - :lon -95.03671349999999, - :house-number "77367", - :street "320th Street", - :city "Olivia", - :state-abbrev "MN", - :zip "56277"} - {:lat 42.5532759, - :lon -71.691813, - :house-number "711", - :street "Reservoir Road", - :city "Lunenburg", - :state-abbrev "MA", - :zip "01462"} - {:lat 32.532624, - :lon -81.277301, - :house-number "372", - :street "Morgan Cemetery Road", - :city "Clyo", - :state-abbrev "GA", - :zip "31303"} - {:lat 40.2045068, - :lon -104.9938637, - :house-number "3171-3339", - :street "Highway 66", - :city "Longmont", - :state-abbrev "CO", - :zip "80504"} - {:lat 42.7140096, - :lon -96.40934569999999, - :house-number "23232-23998", - :street "Fir Avenue", - :city "Merrill", - :state-abbrev "IA", - :zip "51038"} - {:lat 61.738969, - :lon -148.9115811, - :house-number "14154", - :street "Eska Mine Road", - :city "Sutton-Alpine", - :state-abbrev "AK", - :zip "99674"} - {:lat 39.90810070000001, - :lon -78.532979, - :house-number "526", - :street "Rose Lane", - :city "Bedford", - :state-abbrev "PA", - :zip "15522"} - {:lat 41.9988526, - :lon -85.3709988, - :house-number "30011", - :street "Covey Road", - :city "Leonidas", - :state-abbrev "MI", - :zip "49066"} - {:lat 40.575289, - :lon -90.7320816, - :house-number "20001-20347", - :street "County Road 900 East", - :city "Sciota", - :state-abbrev "IL", - :zip "61475"} - {:lat 38.2434457, - :lon -86.80134699999999, - :house-number "9914", - :street "South 475 East", - :city "Ferdinand", - :state-abbrev "IN", - :zip "47532"} - {:lat 61.99231690000001, - :lon -146.7686644, - :house-number "2446", - :street "Glenn Highway", - :city "Glennallen", - :state-abbrev "AK", - :zip "99588"} - {:lat 43.6943312, - :lon -107.8125286, - :house-number "2671", - :street "Lake Creek Road", - :city "Thermopolis", - :state-abbrev "WY", - :zip "82443"} - {:lat 46.3835299, - :lon -104.5119599, - :house-number "220", - :street "Plevna Road", - :city "Plevna", - :state-abbrev "MT", - :zip "59344"} - {:lat 35.0061837, - :lon -97.6163622, - :house-number "13132", - :street "205th Street", - :city "Dibble", - :state-abbrev "OK", - :zip "73031"} - {:lat 42.06306, - :lon -73.1616919, - :house-number "14-22", - :street "Norfolk Road", - :city "Sandisfield", - :state-abbrev "MA", - :zip "01255"} - {:lat 42.772582, - :lon -106.153102, - :house-number "6600", - :street "Hat 6 Road", - :city "Casper", - :state-abbrev "WY", - :zip "82609"} - {:lat 43.9507803, - :lon -72.1719845, - :house-number "1738-1884", - :street "Millpond Road", - :city "Fairlee", - :state-abbrev "VT", - :zip "05045"} - {:lat 45.069139, - :lon -89.90620899999999, - :house-number "10631", - :street "7th Lane", - :city "Athens", - :state-abbrev "WI", - :zip "54411"} - {:lat 44.3252818, - :lon -97.7446155, - :house-number "42300-42398", - :street "211th Street", - :city "Iroquois", - :state-abbrev "SD", - :zip "57353"} - {:lat 42.01385, - :lon -95.7325582, - :house-number "23694", - :street "Sumac Avenue", - :city "Ute", - :state-abbrev "IA", - :zip "51060"} - {:lat 34.9340828, - :lon -88.2797599, - :house-number "28-54", - :street "County Road 324", - :city "Iuka", - :state-abbrev "MS", - :zip "38852"} - {:lat 45.1066001, - :lon -96.5884731, - :house-number "48000-48098", - :street "157th Street", - :city "Revillo", - :state-abbrev "SD", - :zip "57259"} - {:lat 31.052132, - :lon -88.74860989999999, - :house-number "3978", - :street "Merritt Road", - :city "Leakesville", - :state-abbrev "MS", - :zip "39451"} - {:lat 34.3851754, - :lon -88.901736, - :house-number "1034", - :street "Corolla Lane", - :city "Blue Springs", - :state-abbrev "MS", - :zip "38828"} - {:lat 33.92264, - :lon -88.444554, - :house-number "50016", - :street "Moss Road", - :city "Amory", - :state-abbrev "MS", - :zip "38821"} - {:lat 39.5817883, - :lon -102.1110891, - :house-number "36808", - :street "County Road 1", - :city "Idalia", - :state-abbrev "CO", - :zip "80735"} - {:lat 29.881082, - :lon -98.18124999999999, - :house-number "1550", - :street "Casa Sierra", - :city "Canyon Lake", - :state-abbrev "TX", - :zip "78133"} - {:lat 42.683499, - :lon -82.744973, - :house-number "51789", - :street "Base Street", - :city "New Baltimore", - :state-abbrev "MI", - :zip "48047"} - {:lat 46.7782043, - :lon -89.0932449, - :house-number "625", - :street "Depot Street", - :city "Greenland", - :state-abbrev "MI", - :zip "49929"} - {:lat 47.44263600000001, - :lon -99.017718, - :house-number "7151-7175", - :street "2nd Street Northeast", - :city "Carrington", - :state-abbrev "ND", - :zip "58421"} - {:lat 48.0305978, - :lon -110.5805282, - :house-number "2500", - :street "Day Road", - :city "Loma", - :state-abbrev "MT", - :zip "59460"} - {:lat 46.840345, - :lon -67.96735199999999, - :house-number "213", - :street "Kelley Road", - :city "Caribou", - :state-abbrev "ME", - :zip "04736"} - {:lat 38.830162, - :lon -121.025937, - :house-number "4730", - :street "Pilot Creek Lane", - :city "Pilot Hill", - :state-abbrev "CA", - :zip "95664"} - {:lat 43.4787462, - :lon -93.8897504, - :house-number "49347", - :street "40th Avenue", - :city "Buffalo Center", - :state-abbrev "IA", - :zip "50424"} - {:lat 34.3977584, - :lon -85.3889731, - :house-number "1252", - :street "Lyerly Dam Road", - :city "Lyerly", - :state-abbrev "GA", - :zip "30730"} - {:lat 39.093997, - :lon -119.151808, - :house-number "155", - :street "Penrose Lane", - :city "Yerington", - :state-abbrev "NV", - :zip "89447"} - {:lat 36.8575068, - :lon -84.75244339999999, - :house-number "695", - :street "Raleigh Creek Road", - :city "Monticello", - :state-abbrev "KY", - :zip "42633"} - {:lat 37.90008600000001, - :lon -82.99735439999999, - :house-number "7150", - :street "Lower Sand Lick Road", - :city "West Liberty", - :state-abbrev "KY", - :zip "41472"} - {:lat 37.4791753, - :lon -104.557762, - :house-number "30268-30270", - :street "County Road 61", - :city "Aguilar", - :state-abbrev "CO", - :zip "81020"} - {:lat 34.6932203, - :lon -80.3077761, - :house-number "449", - :street "Black Creek Church Road", - :city "Mount Croghan", - :state-abbrev "SC", - :zip "29727"} - {:lat 36.465115, - :lon -94.763875, - :house-number "13527", - :street "East 380 Road", - :city "Jay", - :state-abbrev "OK", - :zip "74346"} - {:lat 31.1153828, - :lon -97.41697579999999, - :house-number "173-285", - :street "Old Waco Road", - :city "Temple", - :state-abbrev "TX", - :zip "76502"} - {:lat 39.3489089, - :lon -105.1765411, - :house-number "413-993", - :street "North Platte River Road", - :city "Sedalia", - :state-abbrev "CO", - :zip "80135"} - {:lat 30.9789516, - :lon -89.0645222, - :house-number "825", - :street "New York Road", - :city "Brooklyn", - :state-abbrev "MS", - :zip "39425"} - {:lat 39.95067299999999, - :lon -79.395112, - :house-number "615", - :street "Clay Run Road", - :city "Mill Run", - :state-abbrev "PA", - :zip "15464"} - {:lat 41.4758628, - :lon -86.2338681, - :house-number "15-381", - :street "Juniper Road", - :city "Bremen", - :state-abbrev "IN", - :zip "46506"} - {:lat 39.1523735, - :lon -87.7098115, - :house-number "12766", - :street "East 2000th Avenue", - :city "West York", - :state-abbrev "IL", - :zip "62478"} - {:lat 38.12026040000001, - :lon -95.76673579999999, - :house-number "653-681", - :street "Kafir Lane", - :city "Burlington", - :state-abbrev "KS", - :zip "66839"} - {:lat 62.10305649999999, - :lon -145.9668141, - :house-number "173", - :street "Glenn Highway", - :city "Glennallen", - :state-abbrev "AK", - :zip "99588"} - {:lat 46.900972, - :lon -122.865224, - :house-number "2648", - :street "Angus Road Southeast", - :city "Tenino", - :state-abbrev "WA", - :zip "98589"} - {:lat 41.3225163, - :lon -81.3750723, - :house-number "850", - :street "South Sussex Court", - :city "Aurora", - :state-abbrev "OH", - :zip "44202"} - {:lat 45.9751596, - :lon -120.399776, - :house-number "78", - :street "Jensen Quarry Road", - :city "Roosevelt", - :state-abbrev "WA", - :zip "99356"} - {:lat 42.193509, - :lon -88.710493, - :house-number "2785", - :street "Garden Prairie Road", - :city "Garden Prairie", - :state-abbrev "IL", - :zip "61038"} - {:lat 44.40173679999999, - :lon -70.9609732, - :house-number "2021", - :street "North Road", - :city "Gilead", - :state-abbrev "ME", - :zip "04217"} - {:lat 39.185932, - :lon -85.1369519, - :house-number "7505", - :street "North Spades Road", - :city "Sunman", - :state-abbrev "IN", - :zip "47041"} - {:lat 60.80175790000001, - :lon -148.9586428, - :house-number "1975", - :street "Wyatt's Windy Road", - :city "Anchorage", - :state-abbrev "AK", - :zip "99587"} - {:lat 57.69668100000001, - :lon -152.547253, - :house-number "12715", - :street "Chiniak Highway", - :city "Kodiak", - :state-abbrev "AK", - :zip "99615"} - {:lat 27.859979, - :lon -81.935053, - :house-number "3001", - :street "Bonnie Mine Road", - :city "Bartow", - :state-abbrev "FL", - :zip "33830"} - {:lat 43.962291, - :lon -101.3931105, - :house-number "23700", - :street "Indian Creek Road", - :city "Kadoka", - :state-abbrev "SD", - :zip "57543"} - {:lat 44.30904899999999, - :lon -120.7914501, - :house-number "4893", - :street "North Ochoco Highway", - :city "Prineville", - :state-abbrev "OR", - :zip "97754"} - {:lat 43.7022358, - :lon -106.4562194, - :house-number "823", - :street "Sussex Road", - :city "Kaycee", - :state-abbrev "WY", - :zip "82639"} - {:lat 30.00989879999999, - :lon -102.6003381, - :house-number "2005", - :street "Longbranch", - :city "Alpine", - :state-abbrev "TX", - :zip "79830"} - {:lat 39.093084, - :lon -97.2875339, - :house-number "537", - :street "3400 Avenue", - :city "Abilene", - :state-abbrev "KS", - :zip "67410"} - {:lat 42.3859311, - :lon -75.8204083, - :house-number "201", - :street "McBerney Road", - :city "Greene", - :state-abbrev "NY", - :zip "13778"} - {:lat 33.5250515, - :lon -81.23591669999999, - :house-number "71", - :street "Off Highway", - :city "Springfield", - :state-abbrev "SC", - :zip "29146"} - {:lat 34.5177893, - :lon -120.426859, - :house-number "6001", - :street "Jalama Road", - :city "Lompoc", - :state-abbrev "CA", - :zip "93436"} - {:lat 40.8553864, - :lon -85.3552304, - :house-number "1608-1798", - :street "North 500 East", - :city "Markle", - :state-abbrev "IN", - :zip "46770"} - {:lat 42.5548728, - :lon -100.7206827, - :house-number "21-30", - :street "290th Avenue", - :city "Valentine", - :state-abbrev "NE", - :zip "69201"} - {:lat 37.8871013, - :lon -111.3800388, - :house-number "1075", - :street "South Draw Lane", - :city "Boulder", - :state-abbrev "UT", - :zip "84716"} - {:lat 45.8947616, - :lon -101.8710636, - :house-number "10371", - :street "212th Avenue", - :city "Keldron", - :state-abbrev "SD", - :zip "57634"} - {:lat 35.0672596, - :lon -87.2733781, - :house-number "349-359", - :street "Richardson Road", - :city "Leoma", - :state-abbrev "TN", - :zip "38468"} - {:lat 62.8626841, - :lon -149.8708804, - :house-number "29161", - :street "North Parks Highway", - :city "Trapper Creek", - :state-abbrev "AK", - :zip "99683"} - {:lat 41.065276, - :lon -94.9103061, - :house-number "1659", - :street "Aspen Avenue", - :city "Villisca", - :state-abbrev "IA", - :zip "50864"} - {:lat 36.228495, - :lon -80.5179206, - :house-number "1018-1024", - :street "Harley Drive", - :city "East Bend", - :state-abbrev "NC", - :zip "27018"} - {:lat 40.4416933, - :lon -107.5048772, - :house-number "6275", - :street "County Road 33", - :city "Craig", - :state-abbrev "CO", - :zip "81625"} - {:lat 46.1798489, - :lon -94.7900583, - :house-number "27676-27698", - :street "Oak Ridge Road", - :city "Browerville", - :state-abbrev "MN", - :zip "56438"} - {:lat 41.23278759999999, - :lon -98.6898791, - :house-number "1378-1390", - :street "Valley Road", - :city "Farwell", - :state-abbrev "NE", - :zip "68838"} - {:lat 40.1116969, - :lon -75.70111469999999, - :house-number "33", - :street "Saint Andrews Lane", - :city "Glenmoore", - :state-abbrev "PA", - :zip "19343"} - {:lat 33.9628991, - :lon -94.4645876, - :house-number "195", - :street "Kornegay Road", - :city "De Queen", - :state-abbrev "AR", - :zip "71832"} - {:lat 47.252835, - :lon -111.2510361, - :house-number "791", - :street "East Eden Road", - :city "Great Falls", - :state-abbrev "MT", - :zip "59405"} - {:lat 34.254886, - :lon -88.9181922, - :house-number "238", - :street "Prewitt Road Extension", - :city "Pontotoc", - :state-abbrev "MS", - :zip "38863"} - {:lat 28.0166763, - :lon -82.3265856, - :house-number "10601", - :street "Bartolotti Loop", - :city "Seffner", - :state-abbrev "FL", - :zip "33584"} - {:lat 42.9727314, - :lon -93.6407687, - :house-number "1435", - :street "Rake Avenue", - :city "Goodell", - :state-abbrev "IA", - :zip "50439"} - {:lat 40.8830528, - :lon -97.711072, - :house-number "1313", - :street "Road G", - :city "York", - :state-abbrev "NE", - :zip "68467"} - {:lat 33.9453372, - :lon -102.8765466, - :house-number "2511-2585", - :street "County Road 97", - :city "Muleshoe", - :state-abbrev "TX", - :zip "79347"} - {:lat 33.028728, - :lon -85.82033799999999, - :house-number "3151", - :street "Lashley Road", - :city "Alexander City", - :state-abbrev "AL", - :zip "35010"} - {:lat 57.2031043, - :lon -153.3069441, - :house-number "3", - :street "3 Saints Avenue", - :city "Old Harbor", - :state-abbrev "AK", - :zip "99643"} - {:lat 43.2271856, - :lon -93.7495422, - :house-number "1201-1245", - :street "320th Street", - :city "Britt", - :state-abbrev "IA", - :zip "50423"} - {:lat 44.234764, - :lon -83.5723488, - :house-number "1655", - :street "Oates Road", - :city "Tawas City", - :state-abbrev "MI", - :zip "48763"} - {:lat 43.05741829999999, - :lon -71.9011538, - :house-number "2368", - :street "2nd New Hampshire Turnpike North", - :city "Deering", - :state-abbrev "NH", - :zip "03244"} - {:lat 39.8053478, - :lon -89.38065110000001, - :house-number "14400", - :street "Bullard Road", - :city "Buffalo", - :state-abbrev "IL", - :zip "62515"} - {:lat 36.464025, - :lon -88.82106499999999, - :house-number "9832", - :street "Reams Road", - :city "South Fulton", - :state-abbrev "TN", - :zip "38257"} - {:lat 46.81518, - :lon -94.628722, - :house-number "298", - :street "County 41 Northwest", - :city "Backus", - :state-abbrev "MN", - :zip "56435"} - {:lat 33.0933287, - :lon -96.80106560000002, - :house-number "8565", - :street "Gratitude Trail", - :city "Plano", - :state-abbrev "TX", - :zip "75024"} - {:lat 47.7854307, - :lon -111.07975, - :house-number "5463", - :street "Carter Road", - :city "Floweree", - :state-abbrev "MT", - :zip "59440"} - {:lat 68.1407585, - :lon -151.7337144, - :house-number "1104", - :street "Summer Street", - :city "Anaktuvuk Pass", - :state-abbrev "AK", - :zip "99721"} - {:lat 36.6480556, - :lon -89.20687989999999, - :house-number "140-148", - :street "County Road 507", - :city "East Prairie", - :state-abbrev "MO", - :zip "63845"} - {:lat 37.466346, - :lon -78.787437, - :house-number "4467", - :street "Wildway Road", - :city "Appomattox", - :state-abbrev "VA", - :zip "24522"} - {:lat 42.1816833, - :lon -72.2562806, - :house-number "1-799", - :street "Smith Road", - :city "Brimfield", - :state-abbrev "MA", - :zip "01010"} - {:lat 38.23524500000001, - :lon -89.9299319, - :house-number "4327", - :street "North Road", - :city "Red Bud", - :state-abbrev "IL", - :zip "62278"} - {:lat 48.0768505, - :lon -103.9195978, - :house-number "14951", - :street "44th Lane Northwest", - :city "Williston", - :state-abbrev "ND", - :zip "58801"} - {:lat 39.5134118, - :lon -83.5542948, - :house-number "298-1500", - :street "Miami Trace Road Northwest", - :city "Washington Court House", - :state-abbrev "OH", - :zip "43160"} - {:lat 38.3767066, - :lon -83.86546, - :house-number "500", - :street "Harvey Point Lane", - :city "Ewing", - :state-abbrev "KY", - :zip "41039"} - {:lat 35.0987129, - :lon -78.44406599999999, - :house-number "1580", - :street "Honeycutt Road", - :city "Clinton", - :state-abbrev "NC", - :zip "28328"} - {:lat 40.2518581, - :lon -111.6709749, - :house-number "701", - :street "Columbia Lane", - :city "Provo", - :state-abbrev "UT", - :zip "84604"} - {:lat 38.2357129, - :lon -84.3932788, - :house-number "421", - :street "Russell Cave Road", - :city "Paris", - :state-abbrev "KY", - :zip "40361"} - {:lat 41.46980569999999, - :lon -75.56494649999999, - :house-number "601-999", - :street "Center Street", - :city "Jessup", - :state-abbrev "PA", - :zip "18434"} - {:lat 40.007307, - :lon -91.379312, - :house-number "6309", - :street "North 24th Street", - :city "Quincy", - :state-abbrev "IL", - :zip "62305"} - {:lat 48.2478274, - :lon -97.4702992, - :house-number "5764-5768", - :street "Carpenter Avenue West", - :city "Forest River", - :state-abbrev "ND", - :zip "58233"} - {:lat 31.8534425, - :lon -95.2631505, - :house-number "4450", - :street "County Road 1707", - :city "Jacksonville", - :state-abbrev "TX", - :zip "75766"} - {:lat 45.53608759999999, - :lon -122.6070854, - :house-number "1823", - :street "Northeast 55th Avenue", - :city "Portland", - :state-abbrev "OR", - :zip "97213"} - {:lat 41.8130925, - :lon -78.1391026, - :house-number "187", - :street "Atkins Road", - :city "Roulette", - :state-abbrev "PA", - :zip "16746"} - {:lat 28.7773258, - :lon -97.38853019999999, - :house-number "7705-8103", - :street "U.S. 77 Alternate", - :city "Goliad", - :state-abbrev "TX", - :zip "77963"} - {:lat 36.798081, - :lon -88.5468652, - :house-number "714", - :street "Heath Lane", - :city "Mayfield", - :state-abbrev "KY", - :zip "42066"} - {:lat 67.08449519999999, - :lon -157.8628762, - :house-number "9998", - :street "Ambler Avenue", - :city "Ambler", - :state-abbrev "AK", - :zip "99786"} - {:lat 38.8718307, - :lon -122.7625342, - :house-number "7947-7949", - :street "Harrington Flat Road", - :city "Kelseyville", - :state-abbrev "CA", - :zip "95451"} - {:lat 35.6170622, - :lon -112.2642372, - :house-number "2779", - :street "Sunaire Avenue", - :city "Williams", - :state-abbrev "AZ", - :zip "86046"} - {:lat 44.972463, - :lon -116.8597028, - :house-number "44600", - :street "Brownlee-Oxbow Highway", - :city "Oxbow", - :state-abbrev "OR", - :zip "97840"} - {:lat 36.1922535, - :lon -82.6589703, - :house-number "2662", - :street "Sand Bar Road", - :city "Chuckey", - :state-abbrev "TN", - :zip "37641"} - {:lat 39.5195547, - :lon -87.1212157, - :house-number "515-521", - :street "South Lambert Street", - :city "Brazil", - :state-abbrev "IN", - :zip "47834"} - {:lat 41.44949889999999, - :lon -81.5390384, - :house-number "19916", - :street "Harvard Avenue", - :city "Warrensville Heights", - :state-abbrev "OH", - :zip "44122"} - {:lat 47.789402, - :lon -117.27539, - :house-number "9613", - :street "East Mount Spokane Park Drive", - :city "Mead", - :state-abbrev "WA", - :zip "99021"} - {:lat 43.7183199, - :lon -111.7442182, - :house-number "7864-7870", - :street "South 1600 East", - :city "Rexburg", - :state-abbrev "ID", - :zip "83440"} - {:lat 40.1236675, - :lon -104.5281548, - :house-number "8505", - :street "County Road 57", - :city "Keenesburg", - :state-abbrev "CO", - :zip "80643"} - {:lat 42.8495931, - :lon -100.9546814, - :house-number "37592", - :street "South Kilgore Road", - :city "Kilgore", - :state-abbrev "NE", - :zip "69216"} - {:lat 39.690456, - :lon -86.58319499999999, - :house-number "3438", - :street "West County Road 500 South", - :city "Clayton", - :state-abbrev "IN", - :zip "46118"} - {:lat 48.107966, - :lon -98.453614, - :house-number "10001-10199", - :street "48th Street Northeast", - :city "Lakota", - :state-abbrev "ND", - :zip "58344"} - {:lat 47.098346, - :lon -91.817061, - :house-number "871", - :street "Stanley Road", - :city "Two Harbors", - :state-abbrev "MN", - :zip "55616"} - {:lat 40.910567, - :lon -88.8735779, - :house-number "19000-19998", - :street "North 300 East Road", - :city "Flanagan", - :state-abbrev "IL", - :zip "61740"} - {:lat 43.4110729, - :lon -99.56864089999999, - :house-number "33049", - :street "275th Street", - :city "Dallas", - :state-abbrev "SD", - :zip "57529"} - {:lat 42.63015499999999, - :lon -103.736465, - :house-number "562", - :street "Andrews Road", - :city "Harrison", - :state-abbrev "NE", - :zip "69346"} - {:lat 40.5366017, - :lon -118.0511495, - :house-number "12490", - :street "Nevada 400", - :city "Imlay", - :state-abbrev "NV", - :zip "89418"} - {:lat 33.4474665, - :lon -79.637743, - :house-number "1468-1814", - :street "County Road S-45-122", - :city "Andrews", - :state-abbrev "SC", - :zip "29510"} - {:lat 46.074001, - :lon -110.0419413, - :house-number "617", - :street "Wheeler Creek Road", - :city "Big Timber", - :state-abbrev "MT", - :zip "59011"} - {:lat 44.6353768, - :lon -123.1255399, - :house-number "31401-31799", - :street "Bryant Way Southwest", - :city "Albany", - :state-abbrev "OR", - :zip "97321"} - {:lat 38.334902, - :lon -96.54696059999999, - :house-number "1871", - :street "Buck Creek Road", - :city "Cottonwood Falls", - :state-abbrev "KS", - :zip "66845"} - {:lat 35.2986389, - :lon -79.54832239999999, - :house-number "4957-4963", - :street "Dowd Road", - :city "West End", - :state-abbrev "NC", - :zip "27376"} - {:lat 44.63497, - :lon -94.87430189999999, - :house-number "72000-72999", - :street "400th Street", - :city "Bird Island", - :state-abbrev "MN", - :zip "55310"} - {:lat 45.0184419, - :lon -89.45760779999999, - :house-number "7494", - :street "Sunrise Road", - :city "Wausau", - :state-abbrev "WI", - :zip "54403"} - {:lat 38.0828436, - :lon -104.138255, - :house-number "59001-62733", - :street "Huerfano Meter Station Road", - :city "Fowler", - :state-abbrev "CO", - :zip "81039"} - {:lat 44.7461575, - :lon -97.22435259999999, - :house-number "44915-44965", - :street "182nd Street", - :city "Hayti", - :state-abbrev "SD", - :zip "57241"} - {:lat 38.84375530000001, - :lon -92.2123464, - :house-number "9301-9351", - :street "South Rangeline Road", - :city "Columbia", - :state-abbrev "MO", - :zip "65201"} - {:lat 43.878749, - :lon -123.004424, - :house-number "81785", - :street "Sears Road", - :city "Creswell", - :state-abbrev "OR", - :zip "97426"} - {:lat 36.067496, - :lon -118.962811, - :house-number "10", - :street "Olive Drive", - :city "Porterville", - :state-abbrev "CA", - :zip "93257"} - {:lat 37.353904, - :lon -80.63106499999999, - :house-number "540", - :street "Big Branch Hollow Road", - :city "Pembroke", - :state-abbrev "VA", - :zip "24136"} - {:lat 44.5013568, - :lon -70.190512, - :house-number "212", - :street "Hyde Road", - :city "Jay", - :state-abbrev "ME", - :zip "04239"} - {:lat 41.7447019, - :lon -95.230696, - :house-number "1648", - :street "Redwood Road", - :city "Kirkman", - :state-abbrev "IA", - :zip "51447"} - {:lat 35.4066559, - :lon -79.91025669999999, - :house-number "R", - :street "Love Joy Road", - :city "Troy", - :state-abbrev "NC", - :zip "27371"} - {:lat 40.24334390000001, - :lon -92.29689409999999, - :house-number "49055", - :street "Aberdeen Avenue", - :city "Baring", - :state-abbrev "MO", - :zip "63531"} - {:lat 68.1407585, - :lon -151.7337144, - :house-number "1104", - :street "Summer Street", - :city "Anaktuvuk Pass", - :state-abbrev "AK", - :zip "99721"} - {:lat 44.75825709999999, - :lon -83.34710319999999, - :house-number "4337", - :street "Sucker Creek Road", - :city "Black River", - :state-abbrev "MI", - :zip "48721"} - {:lat 62.21401100000001, - :lon -149.953529, - :house-number "34200", - :street "South Answer Creek Road", - :city "Talkeetna", - :state-abbrev "AK", - :zip "99676"} - {:lat 43.8345894, - :lon -74.5497638, - :house-number "104", - :street "Carry Lane", - :city "Indian Lake", - :state-abbrev "NY", - :zip "12812"} - {:lat 42.9445834, - :lon -116.06033, - :house-number "37714", - :street "Owyhee Highway", - :city "Grand View", - :state-abbrev "ID", - :zip "83624"} - {:lat 45.270075, - :lon -91.58520299999999, - :house-number "444", - :street "28th Street", - :city "New Auburn", - :state-abbrev "WI", - :zip "54757"} - {:lat 32.139047, - :lon -95.380782, - :house-number "23600", - :street "County Road 181", - :city "Bullard", - :state-abbrev "TX", - :zip "75757"} - {:lat 37.2135641, - :lon -80.5379223, - :house-number "4422-4657", - :street "Mount Zion Road", - :city "Blacksburg", - :state-abbrev "VA", - :zip "24060"} - {:lat 36.6163612, - :lon -94.5197949, - :house-number "656", - :street "Coyote Lane", - :city "Anderson", - :state-abbrev "MO", - :zip "64831"} - {:lat 30.9334562, - :lon -83.8215372, - :house-number "1434", - :street "Patten Coolidge Road", - :city "Thomasville", - :state-abbrev "GA", - :zip "31757"} - {:lat 45.4285494, - :lon -106.0600979, - :house-number "56", - :street "10 Mile Road", - :city "Ashland", - :state-abbrev "MT", - :zip "59003"} - {:lat 32.0184677, - :lon -83.23612779999999, - :house-number "371", - :street "Ball-Adams Road", - :city "Rhine", - :state-abbrev "GA", - :zip "31077"} - {:lat 35.22150310000001, - :lon -83.87637219999999, - :house-number "870", - :street "Morris Creek Road", - :city "Andrews", - :state-abbrev "NC", - :zip "28901"} - {:lat 38.7088689, - :lon -106.2974023, - :house-number "21636-23044", - :street "Chalk Creek Drive", - :city "Nathrop", - :state-abbrev "CO", - :zip "81236"} - {:lat 48.2479813, - :lon -112.7933473, - :house-number "2870", - :street "Heart Butte Road", - :city "Heart Butte", - :state-abbrev "MT", - :zip "59448"} - {:lat 46.2673028, - :lon -94.83111799999999, - :house-number "25651", - :street "440th Street", - :city "Staples", - :state-abbrev "MN", - :zip "56479"} - {:lat 39.8195973, - :lon -106.6488937, - :house-number "10856-10872", - :street "Colorado 131", - :city "Bond", - :state-abbrev "CO", - :zip "80423"} - {:lat 34.6729655, - :lon -90.9358108, - :house-number "1-1245", - :street "Lee Road 146", - :city "Marianna", - :state-abbrev "AR", - :zip "72360"} - {:lat 43.73931109999999, - :lon -73.2099542, - :house-number "1430", - :street "Camp Road", - :city "Hubbardton", - :state-abbrev "VT", - :zip "05733"} - {:lat 44.4667235, - :lon -109.646441, - :house-number "2045-2099", - :street "North Fork Highway", - :city "Cody", - :state-abbrev "WY", - :zip "82414"} - {:lat 31.85133149999999, - :lon -89.56420399999999, - :house-number "8945", - :street "Mississippi 35", - :city "Mize", - :state-abbrev "MS", - :zip "39116"} - {:lat 64.11335319999999, - :lon -145.7153202, - :house-number "4-6", - :street "Berm Road", - :city "Delta Junction", - :state-abbrev "AK", - :zip "99737"} - {:lat 44.08427500000001, - :lon -69.424667, - :house-number "993", - :street "Bremen Road", - :city "Waldoboro", - :state-abbrev "ME", - :zip "04572"} - {:lat 40.8418528, - :lon -95.0637706, - :house-number "2684", - :street "140 Street", - :city "Clarinda", - :state-abbrev "IA", - :zip "51632"} - {:lat 42.5752479, - :lon -83.4225818, - :house-number "4144-4174", - :street "Cedar Avenue", - :city "West Bloomfield Township", - :state-abbrev "MI", - :zip "48323"} - {:lat 37.9000567, - :lon -95.4400805, - :house-number "1100-1140", - :street "1100th Street", - :city "Iola", - :state-abbrev "KS", - :zip "66749"} - {:lat 44.211073, - :lon -116.980414, - :house-number "5370", - :street "Oregon 201", - :city "Ontario", - :state-abbrev "OR", - :zip "97914"} - {:lat 41.69714279999999, - :lon -76.2646924, - :house-number "1439", - :street "Old Stage Coach Road", - :city "Wyalusing", - :state-abbrev "PA", - :zip "18853"} - {:lat 41.517833, - :lon -109.7861365, - :house-number "169-303", - :street "Tenneco Road", - :city "Little America", - :state-abbrev "WY", - :zip "82929"} - {:lat 44.767292, - :lon -117.2465541, - :house-number "41643-41645", - :street "Stanciu Road", - :city "Richland", - :state-abbrev "OR", - :zip "97870"} - {:lat 35.9428891, - :lon -96.5972493, - :house-number "11574", - :street "South Highway 16", - :city "Drumright", - :state-abbrev "OK", - :zip "74030"} - {:lat 47.830185, - :lon -116.902588, - :house-number "17465", - :street "North Reservoir Road", - :city "Rathdrum", - :state-abbrev "ID", - :zip "83858"} - {:lat 37.0261329, - :lon -120.7084354, - :house-number "17767-18027", - :street "Britto Road", - :city "Dos Palos", - :state-abbrev "CA", - :zip "93620"} - {:lat 46.7897253, - :lon -102.3069165, - :house-number "8797-8799", - :street "43rd Street Southwest", - :city "Richardton", - :state-abbrev "ND", - :zip "58652"} - {:lat 39.4218773, - :lon -87.8772173, - :house-number "18895", - :street "North Bluegrass Road", - :city "Martinsville", - :state-abbrev "IL", - :zip "62442"} - {:lat 37.7548839, - :lon -82.11584700000002, - :house-number "1561", - :street "Holly Ridge", - :city "Delbarton", - :state-abbrev "WV", - :zip "25670"} - {:lat 43.6115287, - :lon -75.10066379999999, - :house-number "133", - :street "New York 28", - :city "Forestport", - :state-abbrev "NY", - :zip "13338"} - {:lat 39.5495735, - :lon -74.9842683, - :house-number "977", - :street "Harding Highway", - :city "Newfield", - :state-abbrev "NJ", - :zip "08344"} - {:lat 40.4986933, - :lon -101.4616215, - :house-number "73606", - :street "342 Avenue", - :city "Wauneta", - :state-abbrev "NE", - :zip "69045"} - {:lat 39.88439229999999, - :lon -93.44229159999999, - :house-number "18236", - :street "Liv 216", - :city "Chula", - :state-abbrev "MO", - :zip "64635"} - {:lat 31.6453016, - :lon -98.7931651, - :house-number "6275", - :street "County Road 261", - :city "Zephyr", - :state-abbrev "TX", - :zip "76890"} - {:lat 37.8124198, - :lon -91.809668, - :house-number "13210", - :street "County Road 7480", - :city "Rolla", - :state-abbrev "MO", - :zip "65401"} - {:lat 46.6246575, - :lon -109.9853737, - :house-number "1701", - :street "Lode Road", - :city "Judith Gap", - :state-abbrev "MT", - :zip "59453"} - {:lat 31.7721425, - :lon -88.30609899999999, - :house-number "4243", - :street "County Road 6", - :city "Silas", - :state-abbrev "AL", - :zip "36919"} - {:lat 45.5132811, - :lon -90.7530905, - :house-number "W1521", - :street "U.S. 8", - :city "Hawkins", - :state-abbrev "WI", - :zip "54530"} - {:lat 38.53201540000001, - :lon -77.9146172, - :house-number "18315", - :street "Brenridge Drive", - :city "Brandy Station", - :state-abbrev "VA", - :zip "22714"} - {:lat 31.727264, - :lon -82.86896999999999, - :house-number "922", - :street "Rock Creek Road", - :city "Broxton", - :state-abbrev "GA", - :zip "31519"} - {:lat 37.3029865, - :lon -104.8068505, - :house-number "18926-19804", - :street "County Road 42", - :city "Aguilar", - :state-abbrev "CO", - :zip "81020"} - {:lat 44.7078688, - :lon -101.1166327, - :house-number "24915-24999", - :street "Fosters Bay Road", - :city "Hayes", - :state-abbrev "SD", - :zip "57537"} - {:lat 33.1992752, - :lon -84.7877791, - :house-number "800", - :street "Gold Mine Road", - :city "Grantville", - :state-abbrev "GA", - :zip "30220"} - {:lat 35.532079, - :lon -78.99712, - :house-number "151", - :street "Attie Lee Lane", - :city "Sanford", - :state-abbrev "NC", - :zip "27330"} - {:lat 39.3320421, - :lon -79.8101235, - :house-number "359-433", - :street "Kanetown Road", - :city "Tunnelton", - :state-abbrev "WV", - :zip "26444"} - {:lat 35.5339405, - :lon -103.0605646, - :house-number "8762-8798", - :street "Quay Road C", - :city "Nara Visa", - :state-abbrev "NM", - :zip "88430"} - {:lat 38.87266899999999, - :lon -81.692555, - :house-number "440", - :street "Creed Road", - :city "Sandyville", - :state-abbrev "WV", - :zip "25275"} - {:lat 40.7866224, - :lon -99.41291869999999, - :house-number "2860-3498", - :street "115th Road", - :city "Elm Creek", - :state-abbrev "NE", - :zip "68836"} - {:lat 43.72863599999999, - :lon -89.5407456, - :house-number "N2496", - :street "County Road O", - :city "Endeavor", - :state-abbrev "WI", - :zip "53930"} - {:lat 41.61609139999999, - :lon -80.167881, - :house-number "10862", - :street "Mercer Pike", - :city "Meadville", - :state-abbrev "PA", - :zip "16335"} - {:lat 41.0486853, - :lon -83.2739697, - :house-number "90", - :street "West Street", - :city "New Riegel", - :state-abbrev "OH", - :zip "44853"} - {:lat 44.3047656, - :lon -91.4396951, - :house-number "24455", - :street "Korpal Valley Road", - :city "Arcadia", - :state-abbrev "WI", - :zip "54612"} - {:lat 32.3749628, - :lon -81.91605910000001, - :house-number "1121", - :street "Georgia 46", - :city "Register", - :state-abbrev "GA", - :zip "30452"} - {:lat 43.190175, - :lon -84.290216, - :house-number "19158", - :street "West Brady Road", - :city "Oakley", - :state-abbrev "MI", - :zip "48649"} - {:lat 41.2768919, - :lon -73.3320092, - :house-number "40", - :street "Country Club Lane", - :city "Easton", - :state-abbrev "CT", - :zip "06612"} - {:lat 33.611816, - :lon -101.574374, - :house-number "1212", - :street "County Road 3900", - :city "Lorenzo", - :state-abbrev "TX", - :zip "79343"} - {:lat 30.40228699999999, - :lon -92.4700189, - :house-number "2821", - :street "Grand Coulee Road", - :city "Iota", - :state-abbrev "LA", - :zip "70543"} - {:lat 41.5195341, - :lon -86.2735781, - :house-number "67000", - :street "Block Linden Road", - :city "Lakeville", - :state-abbrev "IN", - :zip "46536"} - {:lat 31.933357, - :lon -98.647925, - :house-number "9151", - :street "Texas 36", - :city "Comanche", - :state-abbrev "TX", - :zip "76442"} - {:lat 45.6637316, - :lon -109.1018165, - :house-number "568", - :street "Pine Crest Road", - :city "Columbus", - :state-abbrev "MT", - :zip "59019"} - {:lat 32.2872424, - :lon -83.76449579999999, - :house-number "1888", - :street "Busby Road", - :city "Unadilla", - :state-abbrev "GA", - :zip "31091"} - {:lat 45.6368764, - :lon -84.80468809999999, - :house-number "5697-5965", - :street "East Levering Road", - :city "Levering", - :state-abbrev "MI", - :zip "49755"} - {:lat 40.3157351, - :lon -103.7817528, - :house-number "21492", - :street "County Road 19.5", - :city "Fort Morgan", - :state-abbrev "CO", - :zip "80701"} - {:lat 30.3464967, - :lon -100.5766679, - :house-number "4783", - :street "Scr 406", - :city "Sonora", - :state-abbrev "TX", - :zip "76950"} - {:lat 34.2315341, - :lon -119.0478149, - :house-number "1731-1753", - :street "Edgemont Drive", - :city "Camarillo", - :state-abbrev "CA", - :zip "93010"} - {:lat 35.320345, - :lon -90.586955, - :house-number "291", - :street "County Road 428", - :city "Parkin", - :state-abbrev "AR", - :zip "72373"} - {:lat 42.2477235, - :lon -71.67822749999999, - :house-number "201", - :street "Westboro Road", - :city "Grafton", - :state-abbrev "MA", - :zip "01536"} - {:lat 68.1407585, - :lon -151.7337144, - :house-number "1104", - :street "Summer Street", - :city "Anaktuvuk Pass", - :state-abbrev "AK", - :zip "99721"} - {:lat 41.8741662, - :lon -76.21114469999999, - :house-number "1047", - :street "Williams Road", - :city "Le Raysville", - :state-abbrev "PA", - :zip "18829"} - {:lat 41.977901, - :lon -77.8008182, - :house-number "340", - :street "Grover Hollow Road", - :city "Genesee", - :state-abbrev "PA", - :zip "16923"} - {:lat 32.5075849, - :lon -83.6623834, - :house-number "1938", - :street "South Houston Lake Road", - :city "Kathleen", - :state-abbrev "GA", - :zip "31047"} - {:lat 36.3514451, - :lon -91.216505, - :house-number "510", - :street "Sassafras Trail", - :city "Ravenden Springs", - :state-abbrev "AR", - :zip "72460"} - {:lat 36.7167521, - :lon -79.360768, - :house-number "282-298", - :street "Kendall Road", - :city "Blairs", - :state-abbrev "VA", - :zip "24527"} - {:lat 39.424406, - :lon -83.364902, - :house-number "8712", - :street "Ohio 753", - :city "Greenfield", - :state-abbrev "OH", - :zip "45123"} - {:lat 38.594246, - :lon -104.2138278, - :house-number "32801-33307", - :street "Myers Road", - :city "Yoder", - :state-abbrev "CO", - :zip "80864"} - {:lat 40.53627960000001, - :lon -96.0678761, - :house-number "1916", - :street "South 42nd Road", - :city "Talmage", - :state-abbrev "NE", - :zip "68448"} - {:lat 40.5008015, - :lon -78.2598843, - :house-number "1150", - :street "Fieldstone Lane", - :city "Hollidaysburg", - :state-abbrev "PA", - :zip "16648"} - {:lat 45.35636909999999, - :lon -98.0547479, - :house-number "40822-40854", - :street "140th Street", - :city "Groton", - :state-abbrev "SD", - :zip "57445"} - {:lat 31.9913681, - :lon -101.9196867, - :house-number "8526-9194", - :street "East County Road 120", - :city "Midland", - :state-abbrev "TX", - :zip "79706"} - {:lat 33.6400715, - :lon -97.244783, - :house-number "6060", - :street "U.S. 82", - :city "Gainesville", - :state-abbrev "TX", - :zip "76240"} - {:lat 37.3464922, - :lon -85.59606199999999, - :house-number "1625", - :street "Hudgins Highway", - :city "Summersville", - :state-abbrev "KY", - :zip "42782"} - {:lat 44.8919432, - :lon -94.599375, - :house-number "58232-58838", - :street "County Saint Aid Highway 12", - :city "Cosmos", - :state-abbrev "MN", - :zip "56228"} - {:lat 48.8773831, - :lon -99.122272, - :house-number "7220-7256", - :street "101st Street Northeast", - :city "Sarles", - :state-abbrev "ND", - :zip "58372"} - {:lat 34.20518, - :lon -91.688007, - :house-number "7620", - :street "Swan Lake Road", - :city "Altheimer", - :state-abbrev "AR", - :zip "72004"} - {:lat 36.862908, - :lon -93.01798409999999, - :house-number "13125", - :street "Missouri 125", - :city "Garrison", - :state-abbrev "MO", - :zip "65657"} - {:lat 46.3151618, - :lon -93.86941999999999, - :house-number "25000-25420", - :street "Emstad Road", - :city "Brainerd", - :state-abbrev "MN", - :zip "56401"} - {:lat 46.9479699, - :lon -105.66358, - :house-number "582", - :street "Bowgun Road", - :city "Terry", - :state-abbrev "MT", - :zip "59349"} - {:lat 45.196964, - :lon -91.28798, - :house-number "24378", - :street "County Highway East", - :city "Cornell", - :state-abbrev "WI", - :zip "54732"} - {:lat 62.457533, - :lon -151.0304531, - :house-number "10086", - :street "Cache Creek Trail", - :city "Petersville", - :state-abbrev "AK", - :zip "99688"} - {:lat 38.7511194, - :lon -105.5306814, - :house-number "845", - :street "County Road 102", - :city "Guffey", - :state-abbrev "CO", - :zip "80820"} - {:lat 40.54277860000001, - :lon -87.213391, - :house-number "4498", - :street "South 600 East", - :city "Oxford", - :state-abbrev "IN", - :zip "47971"} - {:lat 38.369965, - :lon -108.918405, - :house-number "8255", - :street "V Road", - :city "Bedrock", - :state-abbrev "CO", - :zip "81411"} - {:lat 48.032514, - :lon -121.953431, - :house-number "4005", - :street "203rd Avenue Northeast", - :city "Snohomish", - :state-abbrev "WA", - :zip "98290"} - {:lat 45.488054, - :lon -123.0048721, - :house-number "31675", - :street "Southwest Tongue Lane", - :city "Cornelius", - :state-abbrev "OR", - :zip "97113"} - {:lat 60.736956, - :lon -151.204963, - :house-number "52191", - :street "Lucille Drive", - :city "Kenai", - :state-abbrev "AK", - :zip "99611"} - {:lat 47.8002663, - :lon -110.9993894, - :house-number "2111", - :street "Davis School Road", - :city "Carter", - :state-abbrev "MT", - :zip "59420"} - {:lat 34.64605, - :lon -85.357874, - :house-number "333", - :street "Dixon Springs Road", - :city "LaFayette", - :state-abbrev "GA", - :zip "30728"} - {:lat 33.5398494, - :lon -90.5841232, - :house-number "1", - :street "Fox Lane", - :city "Sunflower", - :state-abbrev "MS", - :zip "38778"} - {:lat 37.978329, - :lon -79.320073, - :house-number "165", - :street "High Rock Road", - :city "Raphine", - :state-abbrev "VA", - :zip "24472"} - {:lat 38.0566677, - :lon -95.1343707, - :house-number "11350", - :street "Southeast Trego Road", - :city "Kincaid", - :state-abbrev "KS", - :zip "66039"} - {:lat 41.5546254, - :lon -87.18648069999999, - :house-number "3370", - :street "Reserve Drive", - :city "Portage", - :state-abbrev "IN", - :zip "46368"} - {:lat 30.9257828, - :lon -84.5847308, - :house-number "905", - :street "Moore Street", - :city "Bainbridge", - :state-abbrev "GA", - :zip "39817"} - {:lat 34.919351, - :lon -86.41667699999999, - :house-number "267", - :street "Old Mountain Fork Road", - :city "New Market", - :state-abbrev "AL", - :zip "35761"} - {:lat 35.8908397, - :lon -100.3738468, - :house-number "2550", - :street "South Locust Street", - :city "Canadian", - :state-abbrev "TX", - :zip "79014"} - {:lat 47.553749, - :lon -110.6752015, - :house-number "2430", - :street "Schipf Lane", - :city "Highwood", - :state-abbrev "MT", - :zip "59450"} - {:lat 43.7129598, - :lon -82.8248114, - :house-number "4849", - :street "East Atwater Road", - :city "Minden City", - :state-abbrev "MI", - :zip "48456"} - {:lat 31.9415854, - :lon -85.179998, - :house-number "554-620", - :street "Alabama 6", - :city "Eufaula", - :state-abbrev "AL", - :zip "36027"} - {:lat 41.1433826, - :lon -77.0596565, - :house-number "12090-12170", - :street "Pennsylvania 44", - :city "Allenwood", - :state-abbrev "PA", - :zip "17810"} - {:lat 42.4568889, - :lon -78.98246499999999, - :house-number "11586", - :street "New York 39", - :city "Perrysburg", - :state-abbrev "NY", - :zip "14129"} - {:lat 44.7556077, - :lon -88.8731391, - :house-number "12469", - :street "Grant Road", - :city "Caroline", - :state-abbrev "WI", - :zip "54928"} - {:lat 45.7572842, - :lon -110.2586546, - :house-number "1006-1152", - :street "Convict Grade Road", - :city "Livingston", - :state-abbrev "MT", - :zip "59047"} - {:lat 40.643762, - :lon -74.34552699999999, - :house-number "630", - :street "Westfield Avenue", - :city "Westfield", - :state-abbrev "NJ", - :zip "07090"} - {:lat 35.620295, - :lon -119.173283, - :house-number "14609", - :street "Wallace Road", - :city "McFarland", - :state-abbrev "CA", - :zip "93250"} - {:lat 34.767517, - :lon -88.15534679999999, - :house-number "230", - :street "County Road 995", - :city "Iuka", - :state-abbrev "MS", - :zip "38852"} - {:lat 47.3610646, - :lon -114.7882854, - :house-number "8431", - :street "Montana 200", - :city "Plains", - :state-abbrev "MT", - :zip "59859"} - {:lat 31.6978281, - :lon -93.7426612, - :house-number "12051", - :street "Louisiana 191", - :city "Noble", - :state-abbrev "LA", - :zip "71462"} - {:lat 28.2836389, - :lon -81.3443001, - :house-number "2429", - :street "Academy Circle East", - :city "Kissimmee", - :state-abbrev "FL", - :zip "34744"} - {:lat 40.5483657, - :lon -100.8981094, - :house-number "37054", - :street "Road 740a", - :city "Hayes Center", - :state-abbrev "NE", - :zip "69032"} - {:lat 37.6560882, - :lon -114.4971551, - :house-number "9393", - :street "U.S. 93", - :city "Caliente", - :state-abbrev "NV", - :zip "89008"} - {:lat 69.74188029999999, - :lon -163.0054384, - :house-number "218", - :street "Qigalik Avenue", - :city "Point Lay", - :state-abbrev "AK", - :zip "99759"} - {:lat 37.87920620000001, - :lon -84.8322154, - :house-number "818", - :street "McAfee Lane", - :city "Salvisa", - :state-abbrev "KY", - :zip "40372"} - {:lat 32.2026464, - :lon -96.9182896, - :house-number "321", - :street "Carolyn Lane", - :city "Italy", - :state-abbrev "TX", - :zip "76651"} - {:lat 31.302434, - :lon -103.8943771, - :house-number "662-748", - :street "County Road 229", - :city "Toyah", - :state-abbrev "TX", - :zip "79785"} - {:lat 45.8477683, - :lon -109.9181288, - :house-number "103", - :street "Thompson Lane", - :city "Big Timber", - :state-abbrev "MT", - :zip "59011"} - {:lat 34.7146621, - :lon -83.2255016, - :house-number "251", - :street "State Road S-37-90", - :city "Westminster", - :state-abbrev "SC", - :zip "29693"} - {:lat 42.5941011, - :lon -100.1437794, - :house-number "41616", - :street "U.S. 20", - :city "Johnstown", - :state-abbrev "NE", - :zip "69214"} - {:lat 30.5429963, - :lon -95.2862281, - :house-number "4702", - :street "Texas 150", - :city "New Waverly", - :state-abbrev "TX", - :zip "77358"} - {:lat 27.371848, - :lon -80.3909539, - :house-number "5238", - :street "Northwest Conley Drive", - :city "Port St. Lucie", - :state-abbrev "FL", - :zip "34986"} - {:lat 31.7543016, - :lon -86.8140238, - :house-number "5555", - :street "West Pettibone Road", - :city "Georgiana", - :state-abbrev "AL", - :zip "36033"} - {:lat 44.8285857, - :lon -116.4514573, - :house-number "2679", - :street "Fruitvale Glendale Road", - :city "Fruitvale", - :state-abbrev "ID", - :zip "83612"} - {:lat 36.6824561, - :lon -88.8682925, - :house-number "2072", - :street "Burkett Road", - :city "Clinton", - :state-abbrev "KY", - :zip "42031"} - {:lat 40.1728449, - :lon -99.4659295, - :house-number "71375", - :street "I Road", - :city "Orleans", - :state-abbrev "NE", - :zip "68966"} - {:lat 35.3121184, - :lon -79.5780175, - :house-number "192", - :street "Palomino Road", - :city "Carthage", - :state-abbrev "NC", - :zip "28327"} - {:lat 30.269541, - :lon -93.648978, - :house-number "812", - :street "Louisiana 109", - :city "Starks", - :state-abbrev "LA", - :zip "70661"} - {:lat 38.8532138, - :lon -83.67866110000001, - :house-number "500", - :street "Bob Moore Road", - :city "Winchester", - :state-abbrev "OH", - :zip "45697"} - {:lat 44.9181746, - :lon -84.512378, - :house-number "7912", - :street "Old State Road", - :city "Johannesburg", - :state-abbrev "MI", - :zip "49751"} - {:lat 41.5769338, - :lon -95.73876659999999, - :house-number "2946", - :street "296th Street", - :city "Logan", - :state-abbrev "IA", - :zip "51546"} - {:lat 37.640246, - :lon -113.358102, - :house-number "95", - :street "South Main Street", - :city "Cedar City", - :state-abbrev "UT", - :zip "84720"} - {:lat 39.136784, - :lon -123.340683, - :house-number "10355", - :street "Gap Road", - :city "Ukiah", - :state-abbrev "CA", - :zip "95482"} - {:lat 61.8699257, - :lon -158.1134929, - :house-number "500", - :street "Airport Road", - :city "Crooked Creek", - :state-abbrev "AK", - :zip "99575"} - {:lat 40.2588011, - :lon -83.18504399999999, - :house-number "4105-4155", - :street "Newhouse Road", - :city "Ostrander", - :state-abbrev "OH", - :zip "43061"} - {:lat 42.2350476, - :lon -95.1320111, - :house-number "3835", - :street "Lee Avenue", - :city "Wall Lake", - :state-abbrev "IA", - :zip "51466"} - {:lat 45.2974883, - :lon -98.66082109999999, - :house-number "14400-14498", - :street "377th Avenue", - :city "Mansfield", - :state-abbrev "SD", - :zip "57460"} - {:lat 44.1826097, - :lon -96.9977377, - :house-number "46060", - :street "221st Street", - :city "Nunda", - :state-abbrev "SD", - :zip "57050"} - {:lat 33.9292032, - :lon -86.4874645, - :house-number "345-457", - :street "Industrial Park Road", - :city "Oneonta", - :state-abbrev "AL", - :zip "35121"} - {:lat 42.2030725, - :lon -90.238552, - :house-number "11401-11799", - :street "Illinois 84", - :city "Savanna", - :state-abbrev "IL", - :zip "61074"} - {:lat 38.747285, - :lon -122.436815, - :house-number "25470", - :street "Guenoc Valley Road", - :city "Middletown", - :state-abbrev "CA", - :zip "95461"} - {:lat 45.9996513, - :lon -116.4095479, - :house-number "382", - :street "Moughmer Point Road", - :city "Cottonwood", - :state-abbrev "ID", - :zip "83522"} - {:lat 35.6316119, - :lon -79.74275, - :house-number "3422", - :street "Fairview Farm Road", - :city "Asheboro", - :state-abbrev "NC", - :zip "27205"} - {:lat 37.8924646, - :lon -78.9085721, - :house-number "213", - :street "Wade's Lane", - :city "Nellysford", - :state-abbrev "VA", - :zip "22958"} - {:lat 44.7915107, - :lon -74.3894583, - :house-number "344", - :street "County Highway 13", - :city "North Bangor", - :state-abbrev "NY", - :zip "12966"} - {:lat 41.3518155, - :lon -102.8610079, - :house-number "12207-12499", - :street "Road 50", - :city "Gurley", - :state-abbrev "NE", - :zip "69141"} - {:lat 44.2673703, - :lon -102.897699, - :house-number "15898", - :street "Elk Creek Road", - :city "New Underwood", - :state-abbrev "SD", - :zip "57761"} - {:lat 43.9761662, - :lon -72.0256862, - :house-number "103", - :street "Moses Hill Road", - :city "Piermont", - :state-abbrev "NH", - :zip "03779"} - {:lat 33.911886, - :lon -117.402565, - :house-number "2250", - :street "Saint Lawrence Street", - :city "Riverside", - :state-abbrev "CA", - :zip "92504"} - {:lat 31.3295569, - :lon -96.6387872, - :house-number "9009", - :street "Farm to Market Road 339", - :city "Kosse", - :state-abbrev "TX", - :zip "76653"} - {:lat 43.6163727, - :lon -96.00734779999999, - :house-number "12288-12364", - :street "260th Street", - :city "Adrian", - :state-abbrev "MN", - :zip "56110"} - {:lat 36.3584405, - :lon -82.5067803, - :house-number "456", - :street "Dean Archer Road", - :city "Jonesborough", - :state-abbrev "TN", - :zip "37659"} - {:lat 47.306509, - :lon -114.3258778, - :house-number "8", - :street "Bison Hill Lane", - :city "Dixon", - :state-abbrev "MT", - :zip "59831"} - {:lat 37.883686, - :lon -92.7506329, - :house-number "37128", - :street "Norfolk Drive", - :city "Eldridge", - :state-abbrev "MO", - :zip "65463"} - {:lat 42.955365, - :lon -108.876239, - :house-number "12719", - :street "Us Highway 287", - :city "Lander", - :state-abbrev "WY", - :zip "82520"} - {:lat 37.684682, - :lon -98.3192297, - :house-number "2715-2999", - :street "Northwest 110 Avenue", - :city "Cunningham", - :state-abbrev "KS", - :zip "67035"} - {:lat 37.119296, - :lon -83.3545248, - :house-number "590", - :street "Camp Creek Road", - :city "Wendover", - :state-abbrev "KY", - :zip "41775"} - {:lat 36.0206405, - :lon -91.7991847, - :house-number "535", - :street "Arkansas 58", - :city "Melbourne", - :state-abbrev "AR", - :zip "72556"} - {:lat 30.468483, - :lon -97.17470200000001, - :house-number "112", - :street "Farm to Market Road 112", - :city "Lexington", - :state-abbrev "TX", - :zip "78947"} - {:lat 43.619591, - :lon -116.780469, - :house-number "19426", - :street "Homedale Road", - :city "Caldwell", - :state-abbrev "ID", - :zip "83607"} - {:lat 42.00185099999999, - :lon -88.83209800000002, - :house-number "25591", - :street "Clare Road", - :city "Clare", - :state-abbrev "IL", - :zip "60111"} - {:lat 32.9684008, - :lon -89.72839739999999, - :house-number "3519", - :street "Attala Road 4163", - :city "Sallis", - :state-abbrev "MS", - :zip "39160"} - {:lat 47.5166676, - :lon -101.3110546, - :house-number "3350-3398", - :street "7th Street Northwest", - :city "Coleharbor", - :state-abbrev "ND", - :zip "58531"} - {:lat 38.3699447, - :lon -105.7543789, - :house-number "15587", - :street "U.S. 50", - :city "Coaldale", - :state-abbrev "CO", - :zip "81222"} - {:lat 44.295008, - :lon -99.20564069999999, - :house-number "21301-21397", - :street "349th Avenue", - :city "Ree Heights", - :state-abbrev "SD", - :zip "57371"} - {:lat 46.7837226, - :lon -96.7149852, - :house-number "7001-7499", - :street "40th Street South", - :city "Moorhead", - :state-abbrev "MN", - :zip "56560"} - {:lat 35.2324893, - :lon -97.12338249999999, - :house-number "22979", - :street "Fishmarket Road", - :city "Tecumseh", - :state-abbrev "OK", - :zip "74873"} - {:lat 38.71972, - :lon -104.0339759, - :house-number "14388", - :street "County Road 2", - :city "Rush", - :state-abbrev "CO", - :zip "80833"} - {:lat 45.9914359, - :lon -91.87771029999999, - :house-number "N10010", - :street "Mack Lake Road", - :city "Trego", - :state-abbrev "WI", - :zip "54888"} - {:lat 32.3649355, - :lon -108.6417541, - :house-number "11", - :street "Easy Street", - :city "Lordsburg", - :state-abbrev "NM", - :zip "88045"} - {:lat 30.9223605, - :lon -88.7025561, - :house-number "150", - :street "Erkhart Lane", - :city "Lucedale", - :state-abbrev "MS", - :zip "39452"} - {:lat 34.1874207, - :lon -118.3460122, - :house-number "3300", - :street "West Pacific Avenue", - :city "Burbank", - :state-abbrev "CA", - :zip "91505"} - {:lat 37.0346632, - :lon -84.97187, - :house-number "702", - :street "Parks Ridge Road", - :city "Russell Springs", - :state-abbrev "KY", - :zip "42642"} - {:lat 41.56147989999999, - :lon -101.5703272, - :house-number "605", - :street "Enfield Road", - :city "Arthur", - :state-abbrev "NE", - :zip "69121"} - {:lat 31.7017361, - :lon -92.19979389999999, - :house-number "440", - :street "McClendon Drive", - :city "Trout", - :state-abbrev "LA", - :zip "71371"} - {:lat 48.9376694, - :lon -102.5313156, - :house-number "8301-8355", - :street "105th Street Northwest", - :city "Portal", - :state-abbrev "ND", - :zip "58772"} - {:lat 40.3198608, - :lon -88.603081, - :house-number "2808", - :street "County Road 3425 East", - :city "Farmer City", - :state-abbrev "IL", - :zip "61842"} - {:lat 42.977263, - :lon -73.2044854, - :house-number "162", - :street "Lawrence Road", - :city "Shaftsbury", - :state-abbrev "VT", - :zip "05262"} - {:lat 46.0139827, - :lon -111.3499344, - :house-number "1083-1099", - :street "Broken Creek Road", - :city "Three Forks", - :state-abbrev "MT", - :zip "59752"} - {:lat 30.355476, - :lon -97.077581, - :house-number "5631", - :street "FM 1624 Road", - :city "Lexington", - :state-abbrev "TX", - :zip "78947"} - {:lat 36.6408739, - :lon -121.652028, - :house-number "80", - :street "Hunter Lane", - :city "Salinas", - :state-abbrev "CA", - :zip "93908"} - {:lat 42.691568, - :lon -85.76601199999999, - :house-number "3730", - :street "22nd Street", - :city "Dorr", - :state-abbrev "MI", - :zip "49323"} - {:lat 45.5956919, - :lon -91.7962239, - :house-number "1701-1791", - :street "27th Avenue", - :city "Rice Lake", - :state-abbrev "WI", - :zip "54868"} - {:lat 40.0893774, - :lon -80.94992909999999, - :house-number "69369", - :street "Lee Road", - :city "Saint Clairsville", - :state-abbrev "OH", - :zip "43950"} - {:lat 29.6556693, - :lon -91.3973822, - :house-number "505", - :street "Joey Street", - :city "Patterson", - :state-abbrev "LA", - :zip "70392"} - {:lat 48.33209799999999, - :lon -118.1551135, - :house-number "3126-3312", - :street "Washington 25", - :city "Gifford", - :state-abbrev "WA", - :zip "99131"} - {:lat 39.1427815, - :lon -93.9051427, - :house-number "10844", - :street "County Farm Road", - :city "Lexington", - :state-abbrev "MO", - :zip "64067"} - {:lat 29.3660443, - :lon -96.00103399999999, - :house-number "7654", - :street "County Road 121", - :city "Wharton", - :state-abbrev "TX", - :zip "77488"} - {:lat 33.7677247, - :lon -102.5037282, - :house-number "3600-3680", - :street "Lincoln", - :city "Levelland", - :state-abbrev "TX", - :zip "79336"} - {:lat 35.08888, - :lon -76.727883, - :house-number "373", - :street "Hidden Lane", - :city "Oriental", - :state-abbrev "NC", - :zip "28571"} - {:lat 46.4195137, - :lon -104.9640139, - :house-number "10241", - :street "U.S. 12", - :city "Ismay", - :state-abbrev "MT", - :zip "59336"} - {:lat 34.6336409, - :lon -86.0458366, - :house-number "2912", - :street "South Broad Street", - :city "Scottsboro", - :state-abbrev "AL", - :zip "35769"} - {:lat 41.0274016, - :lon -84.45712999999999, - :house-number "3000-3914", - :street "Road 151", - :city "Grover Hill", - :state-abbrev "OH", - :zip "45849"} - {:lat 41.309987, - :lon -93.62380399999999, - :house-number "9591", - :street "Nevada Street", - :city "Indianola", - :state-abbrev "IA", - :zip "50125"} - {:lat 42.6289348, - :lon -105.6969712, - :house-number "1981-2039", - :street "Spring Canyon Road", - :city "Douglas", - :state-abbrev "WY", - :zip "82633"} - {:lat 34.971496, - :lon -81.42881500000001, - :house-number "1362", - :street "Smith Woods Lane", - :city "Hickory Grove", - :state-abbrev "SC", - :zip "29717"} - {:lat 33.9081019, - :lon -100.9112754, - :house-number "272", - :street "Farm to Market 684", - :city "Roaring Springs", - :state-abbrev "TX", - :zip "79256"} - {:lat 35.36040130000001, - :lon -99.5277927, - :house-number "19471", - :street "East 1130 Road", - :city "Elk City", - :state-abbrev "OK", - :zip "73644"} - {:lat 41.039817, - :lon -93.72575499999999, - :house-number "2525", - :street "Kansas Street", - :city "Osceola", - :state-abbrev "IA", - :zip "50213"} - {:lat 31.2833428, - :lon -97.9102321, - :house-number "7957-7959", - :street "County Road 142", - :city "Gatesville", - :state-abbrev "TX", - :zip "76528"} - {:lat 33.9034747, - :lon -85.63498729999999, - :house-number "137-173", - :street "Pleasant Acres Trail", - :city "Piedmont", - :state-abbrev "AL", - :zip "36272"} - {:lat 35.733558, - :lon -117.9168199, - :house-number "3550", - :street "Grapevine Canyon Road", - :city "Inyokern", - :state-abbrev "CA", - :zip "93527"} - {:lat 44.095176, - :lon -88.237015, - :house-number "N5705", - :street "Vans Road", - :city "Hilbert", - :state-abbrev "WI", - :zip "54129"} - {:lat 39.7562657, - :lon -91.8451163, - :house-number "5201-5599", - :street "County Road 203", - :city "Hunnewell", - :state-abbrev "MO", - :zip "63443"} - {:lat 45.3314532, - :lon -117.96356, - :house-number "62000", - :street "Peach Road", - :city "La Grande", - :state-abbrev "OR", - :zip "97850"} - {:lat 42.4185731, - :lon -114.5851842, - :house-number "2331-2399", - :street "East 2900 North Road", - :city "Twin Falls", - :state-abbrev "ID", - :zip "83301"} - {:lat 43.1830975, - :lon -90.59240489999999, - :house-number "3444", - :street "Wisconsin 133", - :city "Blue River", - :state-abbrev "WI", - :zip "53518"} - {:lat 44.70918169999999, - :lon -95.3287785, - :house-number "77597", - :street "180th Street", - :city "Sacred Heart", - :state-abbrev "MN", - :zip "56285"} - {:lat 42.032549, - :lon -93.557581, - :house-number "4098", - :street "East 13th Street", - :city "Ames", - :state-abbrev "IA", - :zip "50010"} - {:lat 36.805957, - :lon -81.83017, - :house-number "30528", - :street "Old Saltworks Road", - :city "Meadowview", - :state-abbrev "VA", - :zip "24361"} - {:lat 34.7528691, - :lon -104.0238617, - :house-number "3401-3499", - :street "Q R Bh", - :city "McAlister", - :state-abbrev "NM", - :zip "88427"} - {:lat 42.3705256, - :lon -89.59859569999999, - :house-number "715", - :street "East Cedarville Road", - :city "Freeport", - :state-abbrev "IL", - :zip "61032"} - {:lat 44.3222966, - :lon -73.87596239999999, - :house-number "86", - :street "Nye Way", - :city "Wilmington", - :state-abbrev "NY", - :zip "12997"} - {:lat 56.29361249999999, - :lon -158.4068215, - :house-number "100", - :street "Old Cemetery Road", - :city "Chignik", - :state-abbrev "AK", - :zip "99564"} - {:lat 30.423704, - :lon -84.620513, - :house-number "2755", - :street "Cooks Landing Road", - :city "Quincy", - :state-abbrev "FL", - :zip "32351"} - {:lat 37.7368487, - :lon -83.0194695, - :house-number "663", - :street "Rockhouse Fork Road", - :city "Salyersville", - :state-abbrev "KY", - :zip "41465"} - {:lat 37.3628592, - :lon -82.338476, - :house-number "853", - :street "Abner Fork Road", - :city "Belcher", - :state-abbrev "KY", - :zip "41513"} - {:lat 44.8127198, - :lon -71.7810014, - :house-number "2154", - :street "McConnell Pond Road", - :city "Brighton", - :state-abbrev "VT", - :zip "05846"} - {:lat 42.7464915, - :lon -111.3920411, - :house-number "2101-2105", - :street "Slug Creek Road", - :city "Soda Springs", - :state-abbrev "ID", - :zip "83276"} - {:lat 45.5646244, - :lon -103.3030519, - :house-number "15002", - :street "Sd Highway 20", - :city "Reva", - :state-abbrev "SD", - :zip "57651"} - {:lat 38.3596886, - :lon -89.16878779999999, - :house-number "28000-28998", - :street "Hawaii Road", - :city "Ashley", - :state-abbrev "IL", - :zip "62808"} - {:lat 48.310733, - :lon -98.1306968, - :house-number "11501-11599", - :street "62nd Street Northeast", - :city "Adams", - :state-abbrev "ND", - :zip "58210"} - {:lat 34.8671568, - :lon -91.2059261, - :house-number "2002", - :street "U.S. 70", - :city "Brinkley", - :state-abbrev "AR", - :zip "72021"} - {:lat 39.9103911, - :lon -88.6523891, - :house-number "877", - :street "North 400 East Road", - :city "Milmine", - :state-abbrev "IL", - :zip "61855"} - {:lat 37.4425531, - :lon -94.65083899999999, - :house-number "575", - :street "South 250th Street", - :city "Pittsburg", - :state-abbrev "KS", - :zip "66762"} - {:lat 29.6211343, - :lon -82.90534199999999, - :house-number "5600-5698", - :street "Southwest 80th Street", - :city "Trenton", - :state-abbrev "FL", - :zip "32693"} - {:lat 46.6652679, - :lon -95.74012429999999, - :house-number "36087", - :street "South Rose Lake Road", - :city "Frazee", - :state-abbrev "MN", - :zip "56544"} - {:lat 43.3991513, - :lon -123.4633928, - :house-number "664", - :street "Hidden Meadows Lane", - :city "Oakland", - :state-abbrev "OR", - :zip "97462"} - {:lat 41.1820329, - :lon -87.72453, - :house-number "4036", - :street "North 8000 Road East", - :city "Bourbonnais", - :state-abbrev "IL", - :zip "60914"} - {:lat 39.355042, - :lon -91.09216579999999, - :house-number "15674", - :street "Highway Nn", - :city "Bowling Green", - :state-abbrev "MO", - :zip "63334"} - {:lat 36.9549673, - :lon -94.382311, - :house-number "7963", - :street "Lime Kiln Drive", - :city "Neosho", - :state-abbrev "MO", - :zip "64850"} - {:lat 36.5818107, - :lon -97.6177824, - :house-number "17502", - :street "East Blaine Road", - :city "Hunter", - :state-abbrev "OK", - :zip "74640"} - {:lat 32.4850005, - :lon -84.9905705, - :house-number "2205", - :street "2nd Avenue", - :city "Columbus", - :state-abbrev "GA", - :zip "31901"} - {:lat 33.4358134, - :lon -111.5525506, - :house-number "2200-2398", - :street "North Monterey Drive", - :city "Apache Junction", - :state-abbrev "AZ", - :zip "85120"} - {:lat 31.7535979, - :lon -109.431262, - :house-number "4002-4442", - :street "North Rucker Canyon Road", - :city "Elfrida", - :state-abbrev "AZ", - :zip "85610"} - {:lat 37.38131, - :lon -81.027579, - :house-number "114", - :street "Heart Lane", - :city "Princeton", - :state-abbrev "WV", - :zip "24739"} - {:lat 35.913898, - :lon -96.24968299999999, - :house-number "22978", - :street "E0760 Road", - :city "Kellyville", - :state-abbrev "OK", - :zip "74039"} - {:lat 40.033215, - :lon -104.4943459, - :house-number "2017", - :street "County Road 61", - :city "Keenesburg", - :state-abbrev "CO", - :zip "80643"} - {:lat 46.5015639, - :lon -112.1556568, - :house-number "120", - :street "North Fork Travis Creek Road", - :city "Clancy", - :state-abbrev "MT", - :zip "59634"} - {:lat 41.9220929, - :lon -104.0422838, - :house-number "20101-20161", - :street "Lagoon Road", - :city "Lyman", - :state-abbrev "NE", - :zip "69352"} - {:lat 37.0953898, - :lon -94.5471912, - :house-number "335", - :street "North Oak Avenue", - :city "Joplin", - :state-abbrev "MO", - :zip "64801"} - {:lat 33.8428536, - :lon -83.5384707, - :house-number "1241", - :street "Deer Trail", - :city "Bishop", - :state-abbrev "GA", - :zip "30621"} - {:lat 41.28943, - :lon -95.9103964, - :house-number "1414", - :street "Holiday Drive", - :city "Carter Lake", - :state-abbrev "IA", - :zip "51510"} - {:lat 34.1736976, - :lon -102.0168707, - :house-number "400-498", - :street "County Road 100", - :city "Plainview", - :state-abbrev "TX", - :zip "79072"} - {:lat 34.0200917, - :lon -95.04516249999999, - :house-number "1062", - :street "Kamrin Lane", - :city "Valliant", - :state-abbrev "OK", - :zip "74764"} - {:lat 42.92598, - :lon -72.679993, - :house-number "51", - :street "Williams Road", - :city "Newfane", - :state-abbrev "VT", - :zip "05345"} - {:lat 42.47665809999999, - :lon -71.2881029, - :house-number "162", - :street "Hartwell Road", - :city "Bedford", - :state-abbrev "MA", - :zip "01730"} - {:lat 42.4035075, - :lon -95.322609, - :house-number "2600-2698", - :street "Buchanan Avenue", - :city "Arthur", - :state-abbrev "IA", - :zip "51431"} - {:lat 28.1689285, - :lon -97.3016821, - :house-number "291", - :street "Boenig Road", - :city "Woodsboro", - :state-abbrev "TX", - :zip "78393"} - {:lat 42.825532, - :lon -72.11195359999999, - :house-number "69", - :street "Bixler Way", - :city "Jaffrey", - :state-abbrev "NH", - :zip "03452"} - {:lat 40.8992791, - :lon -90.3845278, - :house-number "1200-1250", - :street "Knox Road 300 East", - :city "Galesburg", - :state-abbrev "IL", - :zip "61401"} - {:lat 39.5794897, - :lon -84.79183789999999, - :house-number "9699", - :street "Kingrey Road", - :city "College Corner", - :state-abbrev "OH", - :zip "45003"} - {:lat 42.2946106, - :lon -99.07741209999999, - :house-number "46630", - :street "Benton Road", - :city "Atkinson", - :state-abbrev "NE", - :zip "68713"} - {:lat 41.008449, - :lon -81.4391969, - :house-number "2261", - :street "Wilson Drive", - :city "Akron", - :state-abbrev "OH", - :zip "44312"} - {:lat 33.7699665, - :lon -102.0486106, - :house-number "10302", - :street "County Road 430", - :city "Shallowater", - :state-abbrev "TX", - :zip "79363"} - {:lat 35.4492892, - :lon -99.4039615, - :house-number "10819", - :street "North 2010 Road", - :city "Elk City", - :state-abbrev "OK", - :zip "73644"} - {:lat 34.7891876, - :lon -80.901853, - :house-number "253-265", - :street "Landsford Road", - :city "Catawba", - :state-abbrev "SC", - :zip "29704"} - {:lat 45.21800390000001, - :lon -109.636886, - :house-number "1336", - :street "East Rosebud Road", - :city "Roscoe", - :state-abbrev "MT", - :zip "59071"} - {:lat 47.2702694, - :lon -100.6429055, - :house-number "42000", - :street "93rd Street Northeast", - :city "Regan", - :state-abbrev "ND", - :zip "58477"} - {:lat 46.7179037, - :lon -98.71281859999999, - :house-number "4801", - :street "83rd Avenue Southeast", - :city "Jamestown", - :state-abbrev "ND", - :zip "58401"} - {:lat 44.7383867, - :lon -94.2279976, - :house-number "11843", - :street "U.S. 212", - :city "Glencoe", - :state-abbrev "MN", - :zip "55336"} - {:lat 32.381986, - :lon -80.818321, - :house-number "121", - :street "Okatie Highway", - :city "Okatie", - :state-abbrev "SC", - :zip "29909"} - {:lat 43.771336, - :lon -72.670929, - :house-number "990", - :street "Mount Hunger Road", - :city "Bethel", - :state-abbrev "VT", - :zip "05032"} - {:lat 36.7586199, - :lon -85.1600334, - :house-number "276", - :street "State Highway 639", - :city "Albany", - :state-abbrev "KY", - :zip "42602"} - {:lat 35.4245949, - :lon -114.066813, - :house-number "823", - :street "West Travertine Way", - :city "Kingman", - :state-abbrev "AZ", - :zip "86409"} - {:lat 47.7894416, - :lon -99.995941, - :house-number "2700-2756", - :street "26th Street Northeast", - :city "Harvey", - :state-abbrev "ND", - :zip "58341"} - {:lat 39.4792955, - :lon -84.604654, - :house-number "1130-1390", - :street "West Taylor School Road", - :city "Hamilton", - :state-abbrev "OH", - :zip "45013"} - {:lat 32.6084958, - :lon -88.8978935, - :house-number "1096", - :street "Zion-Hampton Road", - :city "Collinsville", - :state-abbrev "MS", - :zip "39325"} - {:lat 42.38492400000001, - :lon -78.623586, - :house-number "5668", - :street "Town Line Road", - :city "West Valley", - :state-abbrev "NY", - :zip "14171"} - {:lat 44.0669826, - :lon -84.9218827, - :house-number "3560", - :street "Hamilton Road", - :city "Harrison", - :state-abbrev "MI", - :zip "48625"} - {:lat 38.9333055, - :lon -122.3335392, - :house-number "1460", - :street "California 16", - :city "Rumsey", - :state-abbrev "CA", - :zip "95679"} - {:lat 33.1813114, - :lon -94.57768589999999, - :house-number "724", - :street "County Road 2729", - :city "Marietta", - :state-abbrev "TX", - :zip "75566"} - {:lat 33.1996278, - :lon -80.44645919999999, - :house-number "207", - :street "South Railroad Avenue", - :city "Harleyville", - :state-abbrev "SC", - :zip "29448"} - {:lat 35.2066964, - :lon -101.744087, - :house-number "80", - :street "North Lakeside Drive", - :city "Amarillo", - :state-abbrev "TX", - :zip "79118"} - {:lat 44.1963215, - :lon -91.0606095, - :house-number "N3466", - :street "County Road H", - :city "Melrose", - :state-abbrev "WI", - :zip "54642"} - {:lat 41.5529653, - :lon -96.790846, - :house-number "687", - :street "County Road North", - :city "North Bend", - :state-abbrev "NE", - :zip "68649"} - {:lat 40.257934, - :lon -86.02649799999999, - :house-number "685", - :street "West 300 South", - :city "Tipton", - :state-abbrev "IN", - :zip "46072"} - {:lat 30.2058241, - :lon -97.8450228, - :house-number "8102-8104", - :street "Cattle Drive", - :city "Austin", - :state-abbrev "TX", - :zip "78749"} - {:lat 46.1292154, - :lon -95.7333798, - :house-number "35579", - :street "Rabbit Trail", - :city "Ashby", - :state-abbrev "MN", - :zip "56309"} - {:lat 43.319641, - :lon -74.006387, - :house-number "136", - :street "Hadley Hill Road", - :city "Hadley", - :state-abbrev "NY", - :zip "12835"} - {:lat 39.1319872, - :lon -104.4751631, - :house-number "10245", - :street "County Road 74-82", - :city "Peyton", - :state-abbrev "CO", - :zip "80831"} - {:lat 33.5267934, - :lon -86.72717759999999, - :house-number "1522", - :street "Cooper Hill Road", - :city "Birmingham", - :state-abbrev "AL", - :zip "35213"} - {:lat 31.915528, - :lon -97.7665016, - :house-number "2202", - :street "County Road 2130", - :city "Meridian", - :state-abbrev "TX", - :zip "76665"} - {:lat 34.3029723, - :lon -83.40866749999999, - :house-number "373-605", - :street "Georgia 63", - :city "Commerce", - :state-abbrev "GA", - :zip "30529"} - {:lat 37.0660571, - :lon -85.4978884, - :house-number "3542", - :street "Weed-Keltner Road", - :city "Columbia", - :state-abbrev "KY", - :zip "42728"} - {:lat 32.7374535, - :lon -87.1227475, - :house-number "85", - :street "Melton Lane", - :city "Marion", - :state-abbrev "AL", - :zip "36756"} - {:lat 38.3851647, - :lon -78.66171489999999, - :house-number "3675", - :street "Captain Yancey Road", - :city "Elkton", - :state-abbrev "VA", - :zip "22827"} - {:lat 33.1817593, - :lon -102.3888367, - :house-number "1200-1298", - :street "U.S. 380", - :city "Brownfield", - :state-abbrev "TX", - :zip "79316"} - {:lat 42.397973, - :lon -76.48916299999999, - :house-number "155", - :street "Compton Road", - :city "Ithaca", - :state-abbrev "NY", - :zip "14850"} - {:lat 47.74579550000001, - :lon -99.7179276, - :house-number "4001-4045", - :street "23rd Street Northeast", - :city "Harvey", - :state-abbrev "ND", - :zip "58341"} - {:lat 44.5367713, - :lon -88.08122159999999, - :house-number "1973-2050", - :street "Badgerland Drive", - :city "Howard", - :state-abbrev "WI", - :zip "54303"} - {:lat 43.3550494, - :lon -96.1572429, - :house-number "2043", - :street "Harrison Avenue", - :city "Rock Rapids", - :state-abbrev "IA", - :zip "51246"} - {:lat 31.595307, - :lon -97.432699, - :house-number "431", - :street "Mitchell Road", - :city "Valley Mills", - :state-abbrev "TX", - :zip "76689"} - {:lat 37.93648779999999, - :lon -77.8726105, - :house-number "8306", - :street "Jefferson Highway", - :city "Mineral", - :state-abbrev "VA", - :zip "23117"} - {:lat 37.3307973, - :lon -92.9157834, - :house-number "725", - :street "Woodlawn Street", - :city "Marshfield", - :state-abbrev "MO", - :zip "65706"} - {:lat 34.940116, - :lon -86.3316839, - :house-number "1800-1866", - :street "Upper Hurricane Creek Road", - :city "New Market", - :state-abbrev "AL", - :zip "35761"} - {:lat 44.1185313, - :lon -104.1529969, - :house-number "204", - :street "Pzinski Road", - :city "Newcastle", - :state-abbrev "WY", - :zip "82701"} - {:lat 35.898018, - :lon -88.3840419, - :house-number "510", - :street "Pate Road", - :city "Huntingdon", - :state-abbrev "TN", - :zip "38344"} - {:lat 47.599644, - :lon -120.151576, - :house-number "325", - :street "Stephanie Place", - :city "East Wenatchee", - :state-abbrev "WA", - :zip "98802"} - {:lat 45.06441909999999, - :lon -102.7382086, - :house-number "16154", - :street "Cedar Canyon Road", - :city "Faith", - :state-abbrev "SD", - :zip "57626"} - {:lat 31.546573, - :lon -99.009337, - :house-number "13500", - :street "County Road 226", - :city "Brownwood", - :state-abbrev "TX", - :zip "76801"} - {:lat 45.1370999, - :lon -112.956676, - :house-number "7100", - :street "Cold Spring Creek", - :city "Dillon", - :state-abbrev "MT", - :zip "59725"} - {:lat 31.8204453, - :lon -88.8549158, - :house-number "321-403", - :street "Sugar Hill Road", - :city "Shubuta", - :state-abbrev "MS", - :zip "39360"} - {:lat 44.4201989, - :lon -75.811244, - :house-number "1", - :street "Rabbit Island", - :city "Hammond", - :state-abbrev "NY", - :zip "13646"} - {:lat 47.6184896, - :lon -99.2657671, - :house-number "1400-1455", - :street "60th Avenue Northeast", - :city "Cathay", - :state-abbrev "ND", - :zip "58422"} - {:lat 37.2894401, - :lon -84.6981654, - :house-number "4840", - :street "Highway 328", - :city "Eubank", - :state-abbrev "KY", - :zip "42567"} - {:lat 46.771161, - :lon -94.447676, - :house-number "988", - :street "32nd Avenue Southwest", - :city "Backus", - :state-abbrev "MN", - :zip "56435"} - {:lat 43.7059915, - :lon -85.0254995, - :house-number "4614", - :street "North Rolland Road", - :city "Lake", - :state-abbrev "MI", - :zip "48632"} - {:lat 42.4106353, - :lon -102.860152, - :house-number "341", - :street "County Road 59", - :city "Alliance", - :state-abbrev "NE", - :zip "69301"} - {:lat 41.911865, - :lon -72.57235899999999, - :house-number "6", - :street "Mahoney Road", - :city "East Windsor", - :state-abbrev "CT", - :zip "06088"} - {:lat 30.8575926, - :lon -82.0110196, - :house-number "6017", - :street "U.S. 23", - :city "Folkston", - :state-abbrev "GA", - :zip "31537"} - {:lat 48.0111459, - :lon -111.0643719, - :house-number "2129-2679", - :street "Beaverslide Road", - :city "Carter", - :state-abbrev "MT", - :zip "59420"} - {:lat 32.374085, - :lon -97.916224, - :house-number "1121", - :street "Tolar Cemetery Road", - :city "Tolar", - :state-abbrev "TX", - :zip "76476"} - {:lat 41.45168719999999, - :lon -74.938189, - :house-number "200", - :street "German Hill Road", - :city "Shohola", - :state-abbrev "PA", - :zip "18458"} - {:lat 37.933424, - :lon -77.692847, - :house-number "17211", - :street "Tyler Station Road", - :city "Beaverdam", - :state-abbrev "VA", - :zip "23015"} - {:lat 39.010799, - :lon -86.80838299999999, - :house-number "822", - :street "South Coalmine Road", - :city "Bloomfield", - :state-abbrev "IN", - :zip "47424"} - {:lat 39.1563599, - :lon -121.1958697, - :house-number "14272-14502", - :street "Oak Meadow Road", - :city "Penn Valley", - :state-abbrev "CA", - :zip "95946"} - {:lat 32.668459, - :lon -85.596316, - :house-number "10551", - :street "County Road 188", - :city "Waverly", - :state-abbrev "AL", - :zip "36879"} - {:lat 44.986337, - :lon -92.239795, - :house-number "898", - :street "280th Street", - :city "Woodville", - :state-abbrev "WI", - :zip "54028"} - {:lat 43.4996263, - :lon -93.4964936, - :house-number "101-127", - :street "510th Street", - :city "Emmons", - :state-abbrev "MN", - :zip "56029"} - {:lat 35.0676406, - :lon -89.23785559999999, - :house-number "13245", - :street "Lagrange Road", - :city "Grand Junction", - :state-abbrev "TN", - :zip "38039"} - {:lat 33.27755, - :lon -85.9522191, - :house-number "47450", - :street "Alabama 77", - :city "Ashland", - :state-abbrev "AL", - :zip "36251"} - {:lat 39.5205902, - :lon -97.4111349, - :house-number "2750", - :street "Oat Road", - :city "Clyde", - :state-abbrev "KS", - :zip "66938"} - {:lat 30.3817592, - :lon -81.7560161, - :house-number "6854", - :street "Barney Road", - :city "Jacksonville", - :state-abbrev "FL", - :zip "32219"} - {:lat 31.2096126, - :lon -83.60047139999999, - :house-number "800-1110", - :street "Cool Springs Ellenton Road", - :city "Norman Park", - :state-abbrev "GA", - :zip "31771"} - {:lat 37.3628348, - :lon -89.7859974, - :house-number "569-799", - :street "Flint Lane", - :city "Burfordville", - :state-abbrev "MO", - :zip "63739"} - {:lat 44.2172864, - :lon -97.67311819999999, - :house-number "21801-21853", - :street "427th Avenue", - :city "Carthage", - :state-abbrev "SD", - :zip "57323"} - {:lat 33.723889, - :lon -84.0870659, - :house-number "7376", - :street "Union Grove Road", - :city "Lithonia", - :state-abbrev "GA", - :zip "30058"} - {:lat 32.779774, - :lon -92.9706936, - :house-number "1076", - :street "Featherston Road", - :city "Homer", - :state-abbrev "LA", - :zip "71040"} - {:lat 42.494867, - :lon -94.29595499999999, - :house-number "2047", - :street "Hayes Avenue", - :city "Fort Dodge", - :state-abbrev "IA", - :zip "50501"} - {:lat 43.1190903, - :lon -104.0590245, - :house-number "1832", - :street "Boner Road", - :city "Lusk", - :state-abbrev "WY", - :zip "82225"} - {:lat 39.072423, - :lon -94.83926, - :house-number "831", - :street "Lake Forest Drive", - :city "Bonner Springs", - :state-abbrev "KS", - :zip "66012"} - {:lat 36.597885, - :lon -78.63736670000002, - :house-number "6693", - :street "Virginia 49", - :city "Buffalo Junction", - :state-abbrev "VA", - :zip "24529"} - {:lat 46.4311829, - :lon -96.3830335, - :house-number "104", - :street "3rd Street Southwest", - :city "Rothsay", - :state-abbrev "MN", - :zip "56579"} - {:lat 40.7806966, - :lon -98.859955, - :house-number "10850", - :street "Gibbon Road", - :city "Gibbon", - :state-abbrev "NE", - :zip "68840"} - {:lat 36.8577857, - :lon -91.7594467, - :house-number "5322", - :street "County Road 2910", - :city "West Plains", - :state-abbrev "MO", - :zip "65775"} - {:lat 61.3830338, - :lon -145.2370339, - :house-number "56", - :street "Richardson Highway", - :city "Valdez", - :state-abbrev "AK", - :zip "99686"} - {:lat 45.4817989, - :lon -92.3051541, - :house-number "1846-1898", - :street "70th Street", - :city "Balsam Lake", - :state-abbrev "WI", - :zip "54810"} - {:lat 43.5812792, - :lon -72.2928555, - :house-number "597", - :street "Willow Brook Road", - :city "Plainfield", - :state-abbrev "NH", - :zip "03781"} - {:lat 30.369916, - :lon -98.942877, - :house-number "8216", - :street "U.S. 87", - :city "Fredericksburg", - :state-abbrev "TX", - :zip "78624"} - {:lat 43.7176676, - :lon -106.5450203, - :house-number "452", - :street "Sussex Road", - :city "Kaycee", - :state-abbrev "WY", - :zip "82639"} - {:lat 32.584375, - :lon -80.832729, - :house-number "9", - :street "Pocotaligo Place", - :city "Sheldon", - :state-abbrev "SC", - :zip "29941"} - {:lat 34.6600094, - :lon -79.52102119999999, - :house-number "701-713", - :street "State Road S-35-574", - :city "McColl", - :state-abbrev "SC", - :zip "29570"} - {:lat 40.2823179, - :lon -85.30899099999999, - :house-number "6704", - :street "425 East", - :city "Albany", - :state-abbrev "IN", - :zip "47320"} - {:lat 35.8201988, - :lon -79.0349435, - :house-number "3037", - :street "Jack Bennett Road", - :city "Chapel Hill", - :state-abbrev "NC", - :zip "27517"} - {:lat 32.0763808, - :lon -94.52793040000002, - :house-number "6132", - :street "Texas 315", - :city "Carthage", - :state-abbrev "TX", - :zip "75633"} - {:lat 35.393351, - :lon -78.637328, - :house-number "530", - :street "Oak Valley Farm Road", - :city "Coats", - :state-abbrev "NC", - :zip "27521"} - {:lat 46.4859426, - :lon -97.42375179999999, - :house-number "14300-14396", - :street "64th Street Southeast", - :city "Lisbon", - :state-abbrev "ND", - :zip "58054"} - {:lat 34.970647, - :lon -97.40248799999999, - :house-number "25316", - :street "180th Street", - :city "Purcell", - :state-abbrev "OK", - :zip "73080"} - {:lat 37.8900465, - :lon -120.3031323, - :house-number "26255", - :street "Richards Ranch Road", - :city "Sonora", - :state-abbrev "CA", - :zip "95370"} - {:lat 35.7432525, - :lon -86.59663859999999, - :house-number "805-1005", - :street "North Lane", - :city "Eagleville", - :state-abbrev "TN", - :zip "37060"} - {:lat 45.6515423, - :lon -123.0576768, - :house-number "18717", - :street "Northwest Dairy Creek Road", - :city "North Plains", - :state-abbrev "OR", - :zip "97133"} - {:lat 45.2094461, - :lon -94.81758029999999, - :house-number "16511", - :street "Sperry Lake Road", - :city "Atwater", - :state-abbrev "MN", - :zip "56209"} - {:lat 42.9955966, - :lon -92.81076689999999, - :house-number "2461-2499", - :street "Lancer Avenue", - :city "Marble Rock", - :state-abbrev "IA", - :zip "50653"} - {:lat 35.689693, - :lon -101.8942648, - :house-number "69", - :street "Ranch Road 1913", - :city "Channing", - :state-abbrev "TX", - :zip "79018"} - {:lat 30.060675, - :lon -81.66745999999999, - :house-number "2630", - :street "State Road 13", - :city "Fruit Cove", - :state-abbrev "FL", - :zip "32259"} - {:lat 37.91226, - :lon -90.41244999999999, - :house-number "2166", - :street "Fairview Church Road", - :city "Bonne Terre", - :state-abbrev "MO", - :zip "63628"} - {:lat 33.7797056, - :lon -80.85222259999999, - :house-number "358", - :street "Gully Horn Spring Trail", - :city "Saint Matthews", - :state-abbrev "SC", - :zip "29135"} - {:lat 38.5011246, - :lon -91.2857202, - :house-number "8439", - :street "Cedar Fork Road", - :city "New Haven", - :state-abbrev "MO", - :zip "63068"} - {:lat 34.086536, - :lon -85.95671700000001, - :house-number "4931", - :street "Lay Springs Road", - :city "Gadsden", - :state-abbrev "AL", - :zip "35904"} - {:lat 43.5288653, - :lon -92.935665, - :house-number "55501-55999", - :street "120 Street", - :city "Lyle", - :state-abbrev "MN", - :zip "55953"} - {:lat 39.1469534, - :lon -79.6904362, - :house-number "3635", - :street "Cheat Valley Highway", - :city "Parsons", - :state-abbrev "WV", - :zip "26287"} - {:lat 32.1063539, - :lon -92.2867517, - :house-number "787-891", - :street "Busby Road", - :city "Grayson", - :state-abbrev "LA", - :zip "71435"} - {:lat 39.232502, - :lon -121.6801351, - :house-number "3501-3629", - :street "Clark Road", - :city "Live Oak", - :state-abbrev "CA", - :zip "95953"} - {:lat 42.79668, - :lon -83.64693799999999, - :house-number "16073", - :street "Fish Lake Road", - :city "Holly", - :state-abbrev "MI", - :zip "48442"} - {:lat 46.93550270000001, - :lon -99.8257677, - :house-number "3300-3348", - :street "30th Avenue Southeast", - :city "Steele", - :state-abbrev "ND", - :zip "58482"} - {:lat 36.442696, - :lon -119.48305, - :house-number "35909", - :street "California 99", - :city "Kingsburg", - :state-abbrev "CA", - :zip "93631"} - {:lat 47.2240303, - :lon -100.3681224, - :house-number "37000-38298", - :street "262nd Street Northeast", - :city "Wing", - :state-abbrev "ND", - :zip "58494"} - {:lat 29.788228, - :lon -82.588786, - :house-number "14517", - :street "Northwest 232 Street", - :city "High Springs", - :state-abbrev "FL", - :zip "32643"} - {:lat 39.0523873, - :lon -123.4662723, - :house-number "18885", - :street "Philo Greenwood Road", - :city "Philo", - :state-abbrev "CA", - :zip "95466"} - {:lat 46.2231529, - :lon -106.0468284, - :house-number "1305", - :street "Moon Creek Road", - :city "Miles City", - :state-abbrev "MT", - :zip "59301"} - {:lat 33.3027656, - :lon -98.2232921, - :house-number "2472", - :street "Farm to Market Road 2190", - :city "Jacksboro", - :state-abbrev "TX", - :zip "76458"} - {:lat 42.0080615, - :lon -94.21360109999999, - :house-number "2200-2298", - :street "240th Street", - :city "Rippey", - :state-abbrev "IA", - :zip "50235"} - {:lat 30.1970222, - :lon -96.5706678, - :house-number "1265", - :street "Fm 1948 Road North", - :city "Burton", - :state-abbrev "TX", - :zip "77835"} - {:lat 34.6143871, - :lon -98.4092043, - :house-number "501-547", - :street "Northwest 14th Street", - :city "Lawton", - :state-abbrev "OK", - :zip "73507"} - {:lat 48.465387, - :lon -121.565696, - :house-number "54250", - :street "Rockport Cascade Road", - :city "Rockport", - :state-abbrev "WA", - :zip "98283"} - {:lat 41.676573, - :lon -73.828575, - :house-number "805", - :street "Freedom Plains Road", - :city "Poughkeepsie", - :state-abbrev "NY", - :zip "12603"} - {:lat 44.4809454, - :lon -93.6564215, - :house-number "32000-32486", - :street "195th Avenue", - :city "New Prague", - :state-abbrev "MN", - :zip "56071"} - {:lat 43.5590131, - :lon -116.7139159, - :house-number "16168-16246", - :street "Lake Shore Drive", - :city "Caldwell", - :state-abbrev "ID", - :zip "83607"} - {:lat 41.79876890000001, - :lon -81.0127329, - :house-number "7601-7699", - :street "North Ridge Road", - :city "Madison", - :state-abbrev "OH", - :zip "44057"} - {:lat 35.849231, - :lon -78.420177, - :house-number "120", - :street "Winchester Drive", - :city "Wendell", - :state-abbrev "NC", - :zip "27591"} - {:lat 45.9615434, - :lon -95.1162006, - :house-number "22762-23198", - :street "County Road 85", - :city "Osakis", - :state-abbrev "MN", - :zip "56360"} - {:lat 38.7638433, - :lon -122.832752, - :house-number "8770", - :street "Geysers Road", - :city "Geyserville", - :state-abbrev "CA", - :zip "95441"} - {:lat 48.4035234, - :lon -102.9192083, - :house-number "10389", - :street "68th Street Northwest", - :city "Tioga", - :state-abbrev "ND", - :zip "58852"} - {:lat 47.9751825, - :lon -114.1757164, - :house-number "740", - :street "Lutheran Camp Road", - :city "Lakeside", - :state-abbrev "MT", - :zip "59922"} - {:lat 40.9201276, - :lon -92.4560922, - :house-number "15164-15254", - :street "25th Street", - :city "Bloomfield", - :state-abbrev "IA", - :zip "52537"} - {:lat 41.5910944, - :lon -92.3653186, - :house-number "4852-4888", - :street "215th Street", - :city "Deep River", - :state-abbrev "IA", - :zip "52222"} - {:lat 30.404479, - :lon -84.992762, - :house-number "9893", - :street "Northwest 1st Street", - :city "Bristol", - :state-abbrev "FL", - :zip "32321"} - {:lat 40.54611269999999, - :lon -78.578299, - :house-number "232-486", - :street "Bottom Road", - :city "Ashville", - :state-abbrev "PA", - :zip "16613"} - {:lat 41.6681975, - :lon -85.9240149, - :house-number "56901-56999", - :street "Wynridge Circle", - :city "Elkhart", - :state-abbrev "IN", - :zip "46516"} - {:lat 29.88653279999999, - :lon -98.2046063, - :house-number "15288-15770", - :street "Farm to Market Road 306", - :city "Canyon Lake", - :state-abbrev "TX", - :zip "78133"} - {:lat 33.051018, - :lon -80.42415299999999, - :house-number "381", - :street "Cardinal Lane", - :city "Cottageville", - :state-abbrev "SC", - :zip "29435"} - {:lat 35.91618400000001, - :lon -94.035088, - :house-number "12678", - :street "South Whitehouse Road", - :city "Fayetteville", - :state-abbrev "AR", - :zip "72701"} - {:lat 37.494411, - :lon -76.932346, - :house-number "13901", - :street "Mountain Laurel Grove", - :city "Lanexa", - :state-abbrev "VA", - :zip "23089"} - {:lat 37.1418162, - :lon -120.4488007, - :house-number "2331", - :street "East Roosevelt Road", - :city "Merced", - :state-abbrev "CA", - :zip "95341"} - {:lat 44.6405197, - :lon -100.3246272, - :house-number "18959", - :street "291st Avenue", - :city "Pierre", - :state-abbrev "SD", - :zip "57501"} - {:lat 47.6043511, - :lon -110.8961427, - :house-number "2086-2186", - :street "Shepherd Crossing Road", - :city "Highwood", - :state-abbrev "MT", - :zip "59450"} - {:lat 48.8363749, - :lon -119.5416849, - :house-number "42", - :street "Offwhite Rock Road", - :city "Tonasket", - :state-abbrev "WA", - :zip "98855"} - {:lat 37.8920912, - :lon -83.46088739999999, - :house-number "210", - :street "Harrold Rose Road", - :city "Ezel", - :state-abbrev "KY", - :zip "41425"} - {:lat 46.1943079, - :lon -94.9694342, - :house-number "18280-18998", - :street "County Road 22", - :city "Clarissa", - :state-abbrev "MN", - :zip "56440"} - {:lat 33.1185029, - :lon -94.954814, - :house-number "1B", - :street "Southeast", - :city "Mount Pleasant", - :state-abbrev "TX", - :zip "75455"} - {:lat 35.54464189999999, - :lon -92.3177146, - :house-number "1352", - :street "Clella Circle", - :city "Bee Branch", - :state-abbrev "AR", - :zip "72013"} - {:lat 30.5741978, - :lon -90.2788624, - :house-number "27004", - :street "Teeney Weeney Lane", - :city "Folsom", - :state-abbrev "LA", - :zip "70437"} - {:lat 43.4391224, - :lon -118.5573503, - :house-number "65053", - :street "Crane Buchanan Road", - :city "Burns", - :state-abbrev "OR", - :zip "97720"} - {:lat 45.9334388, - :lon -95.5630416, - :house-number "12035-12483", - :street "39th Avenue Northwest", - :city "Garfield", - :state-abbrev "MN", - :zip "56332"} - {:lat 35.5732073, - :lon -79.3498272, - :house-number "1000-1398", - :street "Roberts Chapel Road", - :city "Goldston", - :state-abbrev "NC", - :zip "27252"} - {:lat 45.2415995, - :lon -97.8162801, - :house-number "14786", - :street "420th Avenue", - :city "Bristol", - :state-abbrev "SD", - :zip "57219"} - {:lat 30.91589999999999, - :lon -83.3531, - :house-number "4443", - :street "Mathis Mill Road", - :city "Valdosta", - :state-abbrev "GA", - :zip "31602"} - {:lat 37.4452308, - :lon -97.5756004, - :house-number "627", - :street "West 130th Avenue North", - :city "Conway Springs", - :state-abbrev "KS", - :zip "67031"} - {:lat 43.9979139, - :lon -91.20442670000001, - :house-number "W5941", - :street "M Johnson Road", - :city "Holmen", - :state-abbrev "WI", - :zip "54636"} - {:lat 40.6198602, - :lon -101.106952, - :house-number "74384", - :street "Avenue 359", - :city "Hayes Center", - :state-abbrev "NE", - :zip "69032"} - {:lat 46.4128687, - :lon -96.6998875, - :house-number "17701-17799", - :street "69th Street Southeast", - :city "Wahpeton", - :state-abbrev "ND", - :zip "58075"} - {:lat 31.8095954, - :lon -90.04842359999999, - :house-number "795-805", - :street "Shivers Road", - :city "Pinola", - :state-abbrev "MS", - :zip "39149"} - {:lat 31.0174011, - :lon -83.6686545, - :house-number "1301", - :street "Branch Road", - :city "Pavo", - :state-abbrev "GA", - :zip "31778"} - {:lat 62.05103500000001, - :lon -150.721023, - :house-number "50600", - :street "South Oilwell Road", - :city "Willow", - :state-abbrev "AK", - :zip "99688"} - {:lat 39.2767089, - :lon -103.4118139, - :house-number "32372", - :street "County Road 3g", - :city "Genoa", - :state-abbrev "CO", - :zip "80818"} - {:lat 37.1011789, - :lon -77.0112626, - :house-number "6040", - :street "Carsley Road", - :city "Waverly", - :state-abbrev "VA", - :zip "23890"} - {:lat 43.699798, - :lon -71.5391219, - :house-number "115", - :street "East Holderness Road", - :city "Holderness", - :state-abbrev "NH", - :zip "03245"} - {:lat 43.0698422, - :lon -93.67516789999999, - :house-number "2110-2198", - :street "Palm Avenue", - :city "Garner", - :state-abbrev "IA", - :zip "50438"} - {:lat 27.2793536, - :lon -98.32633179999999, - :house-number "1148-1472", - :street "County Road 238", - :city "Concepcion", - :state-abbrev "TX", - :zip "78349"} - {:lat 45.6421455, - :lon -93.67648679999999, - :house-number "6594-6850", - :street "130th Avenue", - :city "Princeton", - :state-abbrev "MN", - :zip "55371"} - {:lat 36.2373819, - :lon -96.18286499999999, - :house-number "4", - :street "Osage Pass", - :city "Sand Springs", - :state-abbrev "OK", - :zip "74063"} - {:lat 40.2783021, - :lon -90.5944555, - :house-number "16000-16498", - :street "County Road 00 North", - :city "Industry", - :state-abbrev "IL", - :zip "61440"} - {:lat 44.1457049, - :lon -91.4179048, - :house-number "W23274", - :street "German Coulee Lane", - :city "Galesville", - :state-abbrev "WI", - :zip "54630"} - {:lat 39.8587032, - :lon -76.1208754, - :house-number "244", - :street "Wesley Road", - :city "Quarryville", - :state-abbrev "PA", - :zip "17566"} - {:lat 43.254214, - :lon -87.97157399999999, - :house-number "4550", - :street "Highland Road", - :city "Thiensville", - :state-abbrev "WI", - :zip "53092"} - {:lat 33.4625962, - :lon -97.20268800000001, - :house-number "2225", - :street "FM 2848", - :city "Valley View", - :state-abbrev "TX", - :zip "76272"} - {:lat 39.906048, - :lon -84.30404, - :house-number "401", - :street "North Main Street", - :city "Englewood", - :state-abbrev "OH", - :zip "45322"} - {:lat 43.7038961, - :lon -89.6077495, - :house-number "3701-3793", - :street "1st Lane", - :city "Oxford", - :state-abbrev "WI", - :zip "53952"} - {:lat 34.8764255, - :lon -79.9231124, - :house-number "815", - :street "Cairo Road", - :city "Morven", - :state-abbrev "NC", - :zip "28119"} - {:lat 46.1814928, - :lon -89.7852925, - :house-number "6945", - :street "County Road P", - :city "Manitowish Waters", - :state-abbrev "WI", - :zip "54545"} - {:lat 48.412799, - :lon -103.836241, - :house-number "6894", - :street "146th Avenue Northwest", - :city "Williston", - :state-abbrev "ND", - :zip "58801"} - {:lat 47.6064546, - :lon -91.6451022, - :house-number "601-685", - :street "US for Service Highway 15", - :city "Isabella", - :state-abbrev "MN", - :zip "55607"} - {:lat 44.0120672, - :lon -83.8757284, - :house-number "1700-1762", - :street "Wyatt Road", - :city "Standish", - :state-abbrev "MI", - :zip "48658"} - {:lat 31.36881, - :lon -94.6864782, - :house-number "9", - :street "Farm to Market 842", - :city "Lufkin", - :state-abbrev "TX", - :zip "75901"} - {:lat 30.8445826, - :lon -84.61763049999999, - :house-number "114", - :street "Pine Street", - :city "Bainbridge", - :state-abbrev "GA", - :zip "39819"} - {:lat 43.200799, - :lon -77.502127, - :house-number "671", - :street "Strand Pond Circle", - :city "Webster", - :state-abbrev "NY", - :zip "14580"} - {:lat 42.755688, - :lon -105.088047, - :house-number "150", - :street "Wintermote Road", - :city "Douglas", - :state-abbrev "WY", - :zip "82633"} - {:lat 46.1078384, - :lon -95.6962695, - :house-number "38045", - :street "County Highway 64", - :city "Ashby", - :state-abbrev "MN", - :zip "56309"} - {:lat 35.499251, - :lon -78.6740839, - :house-number "1220", - :street "Chisenhall Road", - :city "Angier", - :state-abbrev "NC", - :zip "27501"} - {:lat 30.3864414, - :lon -88.69777719999999, - :house-number "5504", - :street "Lighthouse Circle", - :city "Gautier", - :state-abbrev "MS", - :zip "39553"} - {:lat 42.0179096, - :lon -96.80242539999999, - :house-number "1213-1299", - :street "T Road", - :city "Wisner", - :state-abbrev "NE", - :zip "68791"} - {:lat 38.8145304, - :lon -93.7133188, - :house-number "330-422", - :street "Northeast 151 Street", - :city "Warrensburg", - :state-abbrev "MO", - :zip "64093"} - {:lat 31.6480268, - :lon -81.875192, - :house-number "1369", - :street "Beechwood Drive", - :city "Jesup", - :state-abbrev "GA", - :zip "31545"} - {:lat 37.2711474, - :lon -93.6152909, - :house-number "289-499", - :street "Lawrence 1245", - :city "Ash Grove", - :state-abbrev "MO", - :zip "65604"} - {:lat 35.610449, - :lon -88.659165, - :house-number "3864", - :street "Beech Bluff Road", - :city "Beech Bluff", - :state-abbrev "TN", - :zip "38313"} - {:lat 41.6704094, - :lon -75.9024223, - :house-number "604", - :street "Quarry Road", - :city "Springville", - :state-abbrev "PA", - :zip "18844"} - {:lat 35.147371, - :lon -118.869354, - :house-number "2909", - :street "Herring Road", - :city "Arvin", - :state-abbrev "CA", - :zip "93203"} - {:lat 40.8147975, - :lon -73.7218001, - :house-number "448", - :street "East Shore Road", - :city "Kings Point", - :state-abbrev "NY", - :zip "11024"} - {:lat 46.68863899999999, - :lon -91.718226, - :house-number "2078", - :street "South Rudolphs Road", - :city "Maple", - :state-abbrev "WI", - :zip "54854"} - {:lat 42.5379342, - :lon -94.2998509, - :house-number "1700-1776", - :street "175th Street", - :city "Fort Dodge", - :state-abbrev "IA", - :zip "50501"} - {:lat 42.236748, - :lon -79.576594, - :house-number "8053", - :street "Hannum Road", - :city "Mayville", - :state-abbrev "NY", - :zip "14757"} - {:lat 44.1525906, - :lon -103.3311907, - :house-number "6700", - :street "Ridgeview Drive", - :city "Black Hawk", - :state-abbrev "SD", - :zip "57718"} - {:lat 43.7324515, - :lon -90.05891059999999, - :house-number "2622", - :street "County Road K", - :city "Mauston", - :state-abbrev "WI", - :zip "53948"} - {:lat 43.3452106, - :lon -108.5395218, - :house-number "376-586", - :street "North Muddy Road", - :city "Riverton", - :state-abbrev "WY", - :zip "82501"} - {:lat 40.9983, - :lon -96.02075900000001, - :house-number "311", - :street "South 18th Street", - :city "Plattsmouth", - :state-abbrev "NE", - :zip "68048"} - {:lat 37.3743483, - :lon -86.6127781, - :house-number "4623", - :street "Oak Ridge Road", - :city "Morgantown", - :state-abbrev "KY", - :zip "42261"} - {:lat 41.3678818, - :lon -79.4598893, - :house-number "971-1135", - :street "Gowdy Road", - :city "Venus", - :state-abbrev "PA", - :zip "16364"} - {:lat 47.9948865, - :lon -101.7226229, - :house-number "28201-28869", - :street "233rd Avenue Southwest", - :city "Ryder", - :state-abbrev "ND", - :zip "58779"} - {:lat 28.263284, - :lon -82.2049866, - :house-number "7247", - :street "Fort King Road", - :city "Zephyrhills", - :state-abbrev "FL", - :zip "33541"} - {:lat 36.0779349, - :lon -77.813864, - :house-number "9970", - :street "Watson Seed Farm Road", - :city "Whitakers", - :state-abbrev "NC", - :zip "27891"} - {:lat 39.9127209, - :lon -82.4083883, - :house-number "14462-14560", - :street "Township Road 1061", - :city "Thornville", - :state-abbrev "OH", - :zip "43076"} - {:lat 45.0796555, - :lon -122.0547712, - :house-number "59870-61498", - :street "Clackamas Highway", - :city "Estacada", - :state-abbrev "OR", - :zip "97023"} - {:lat 59.83919119999999, - :lon -151.6843778, - :house-number "29675", - :street "Komsolmol Street", - :city "Anchor Point", - :state-abbrev "AK", - :zip "99556"} - {:lat 44.1727998, - :lon -68.69405200000001, - :house-number "3", - :street "Burnt Cove Road", - :city "Stonington", - :state-abbrev "ME", - :zip "04681"} - {:lat 42.981382, - :lon -78.589715, - :house-number "10684", - :street "Main Street", - :city "Clarence", - :state-abbrev "NY", - :zip "14031"} - {:lat 40.3666232, - :lon -96.6132913, - :house-number "16467", - :street "South 82 Road", - :city "Pickrell", - :state-abbrev "NE", - :zip "68422"} - {:lat 37.2269742, - :lon -119.8703536, - :house-number "36728-36798", - :street "Road 606", - :city "Raymond", - :state-abbrev "CA", - :zip "93653"} - {:lat 41.46605, - :lon -100.4432582, - :house-number "198", - :street "County Road 60", - :city "Stapleton", - :state-abbrev "NE", - :zip "69163"} - {:lat 44.3227491, - :lon -90.0942797, - :house-number "7750", - :street "Michalik Lane", - :city "Wisconsin Rapids", - :state-abbrev "WI", - :zip "54495"} - {:lat 48.047291, - :lon -120.37601, - :house-number "25", - :street "Mile Creek Road", - :city "Chelan", - :state-abbrev "WA", - :zip "98816"} - {:lat 37.62904899999999, - :lon -97.736464, - :house-number "3401", - :street "South 343rd Street West", - :city "Cheney", - :state-abbrev "KS", - :zip "67025"} - {:lat 33.5128449, - :lon -97.44727350000001, - :house-number "5477", - :street "County Road 343", - :city "Forestburg", - :state-abbrev "TX", - :zip "76239"} - {:lat 32.6380644, - :lon -83.18637799999999, - :house-number "200", - :street "George Gray Rd", - :city "Danville", - :state-abbrev "GA", - :zip "31017"} - {:lat 35.511519, - :lon -80.68258399999999, - :house-number "6101", - :street "Wright Road", - :city "Kannapolis", - :state-abbrev "NC", - :zip "28081"} - {:lat 40.4097473, - :lon -90.89424960000001, - :house-number "8600", - :street "East 90th Street", - :city "Tennessee", - :state-abbrev "IL", - :zip "62374"} - {:lat 33.4477085, - :lon -80.6425399, - :house-number "175", - :street "Bronze Oak Court", - :city "Bowman", - :state-abbrev "SC", - :zip "29018"} - {:lat 36.5703326, - :lon -88.1879546, - :house-number "3261", - :street "Cherry Corner Road", - :city "Murray", - :state-abbrev "KY", - :zip "42071"} - {:lat 43.1903145, - :lon -74.82838389999999, - :house-number "465", - :street "Jerseyfield Road", - :city "Little Falls", - :state-abbrev "NY", - :zip "13365"} - {:lat 33.3826153, - :lon -117.1752984, - :house-number "395", - :street "Old Highway 395", - :city "Fallbrook", - :state-abbrev "CA", - :zip "92028"} - {:lat 34.0129499, - :lon -81.25298099999999, - :house-number "313", - :street "Newridge Road", - :city "Lexington", - :state-abbrev "SC", - :zip "29072"} - {:lat 34.534264, - :lon -80.3575819, - :house-number "99", - :street "Private Lane", - :city "Bethune", - :state-abbrev "SC", - :zip "29009"} - {:lat 39.9149203, - :lon -76.2466936, - :house-number "384", - :street "Lancaster Pike", - :city "New Providence", - :state-abbrev "PA", - :zip "17560"} - {:lat 44.5399699, - :lon -93.112949, - :house-number "2226", - :street "280th Street West", - :city "Northfield", - :state-abbrev "MN", - :zip "55057"} - {:lat 39.4671914, - :lon -96.5883061, - :house-number "18576-19008", - :street "Long Parkway Road", - :city "Olsburg", - :state-abbrev "KS", - :zip "66520"} - {:lat 39.92080929999999, - :lon -88.70722769999999, - :house-number "850-898", - :street "North 200 East Road", - :city "Cerro Gordo", - :state-abbrev "IL", - :zip "61818"} - {:lat 38.109368, - :lon -120.931083, - :house-number "9414", - :street "Warren Road", - :city "Valley Springs", - :state-abbrev "CA", - :zip "95252"} - {:lat 38.806465, - :lon -97.922608, - :house-number "1801", - :street "South Eff Creek Road", - :city "Brookville", - :state-abbrev "KS", - :zip "67425"} - {:lat 44.1058552, - :lon -89.94022939999999, - :house-number "1003-1075", - :street "County Highway Z", - :city "Arkdale", - :state-abbrev "WI", - :zip "54613"} - {:lat 36.8655304, - :lon -121.4419804, - :house-number "2136-2598", - :street "Wright Road", - :city "Hollister", - :state-abbrev "CA", - :zip "95023"} - {:lat 30.92620659999999, - :lon -93.9435721, - :house-number "766", - :street "County Road 293", - :city "Jasper", - :state-abbrev "TX", - :zip "75951"} - {:lat 34.9475899, - :lon -81.5415228, - :house-number "3057", - :street "State Road S-11-54", - :city "Gaffney", - :state-abbrev "SC", - :zip "29340"} - {:lat 37.1022152, - :lon -96.3491523, - :house-number "1076", - :street "Heritage Road", - :city "Sedan", - :state-abbrev "KS", - :zip "67361"} - {:lat 43.4055569, - :lon -93.6590501, - :house-number "44386", - :street "160th Avenue", - :city "Leland", - :state-abbrev "IA", - :zip "50453"} - {:lat 46.0147823, - :lon -84.0355428, - :house-number "15341-15999", - :street "East North Caribou Lake Road", - :city "De Tour Village", - :state-abbrev "MI", - :zip "49725"} - {:lat 31.9162303, - :lon -98.7254346, - :house-number "801", - :street "County Road 153", - :city "Comanche", - :state-abbrev "TX", - :zip "76442"} - {:lat 40.4061175, - :lon -83.60290359999999, - :house-number "2096-2398", - :street "Hamilton Street", - :city "West Mansfield", - :state-abbrev "OH", - :zip "43358"} - {:lat 33.56305150000001, - :lon -79.94783749999999, - :house-number "1096", - :street "McMillan Road", - :city "Greeleyville", - :state-abbrev "SC", - :zip "29056"} - {:lat 43.1217277, - :lon -91.70698449999999, - :house-number "1543-1583", - :street "U.S. 52", - :city "Castalia", - :state-abbrev "IA", - :zip "52133"} - {:lat 47.1021951, - :lon -118.3224433, - :house-number "1200-1298", - :street "North Benzel Road", - :city "Ritzville", - :state-abbrev "WA", - :zip "99169"} - {:lat 48.8441187, - :lon -105.5076285, - :house-number "481", - :street "French Lane", - :city "Scobey", - :state-abbrev "MT", - :zip "59263"} - {:lat 32.4712106, - :lon -93.7013435, - :house-number "300-1098", - :street "East Preston Avenue", - :city "Shreveport", - :state-abbrev "LA", - :zip "71105"} - {:lat 33.0973029, - :lon -84.2695906, - :house-number "248-298", - :street "East Milner Road", - :city "Zebulon", - :state-abbrev "GA", - :zip "30295"} - {:lat 43.0992814, - :lon -84.7479912, - :house-number "11890", - :street "Fitzpatrick Road", - :city "Fowler", - :state-abbrev "MI", - :zip "48835"} - {:lat 43.16911330000001, - :lon -76.3968835, - :house-number "1585-1593", - :street "West Genesee Road", - :city "Baldwinsville", - :state-abbrev "NY", - :zip "13027"} - {:lat 33.7403946, - :lon -89.64976779999999, - :house-number "735", - :street "Providence Road", - :city "Grenada", - :state-abbrev "MS", - :zip "38901"} - {:lat 36.0559801, - :lon -94.5138154, - :house-number "13683", - :street "Cincinnati Creek Road", - :city "Summers", - :state-abbrev "AR", - :zip "72769"} - {:lat 33.6022302, - :lon -87.9937408, - :house-number "6322-6726", - :street "County Road 49", - :city "Kennedy", - :state-abbrev "AL", - :zip "35574"} - {:lat 29.1215861, - :lon -96.7785579, - :house-number "11434", - :street "State Highway 111 North", - :city "Edna", - :state-abbrev "TX", - :zip "77957"} - {:lat 39.81943580000001, - :lon -88.6510236, - :house-number "151-299", - :street "North 500 East Road", - :city "Hammond", - :state-abbrev "IL", - :zip "61929"} - {:lat 38.4334314, - :lon -102.4183399, - :house-number "11500-11998", - :street "County Road 60", - :city "Sheridan Lake", - :state-abbrev "CO", - :zip "81071"} - {:lat 35.289251, - :lon -77.2919639, - :house-number "585", - :street "Core Creek Landing Road", - :city "Dover", - :state-abbrev "NC", - :zip "28526"} - {:lat 45.8947399, - :lon -104.30428, - :house-number "1131", - :street "Mill Iron Camp Crook Road", - :city "Ekalaka", - :state-abbrev "MT", - :zip "59324"} - {:lat 47.13607529999999, - :lon -105.3248207, - :house-number "640-726", - :street "Road 209", - :city "Terry", - :state-abbrev "MT", - :zip "59349"} - {:lat 29.3509994, - :lon -90.48580489999999, - :house-number "2211", - :street "South Madison Road", - :city "Montegut", - :state-abbrev "LA", - :zip "70377"} - {:lat 38.840185, - :lon -76.679547, - :house-number "801", - :street "Ben Jones Lane", - :city "Lothian", - :state-abbrev "MD", - :zip "20711"} - {:lat 36.068326, - :lon -87.443428, - :house-number "811", - :street "Furnace Hollow Road", - :city "Dickson", - :state-abbrev "TN", - :zip "37055"} - {:lat 44.3350127, - :lon -69.2917356, - :house-number "2383", - :street "Collinstown Road", - :city "Appleton", - :state-abbrev "ME", - :zip "04862"} - {:lat 39.1604578, - :lon -93.84383489999999, - :house-number "16638-16644", - :street "Linwood Lawn Drive", - :city "Lexington", - :state-abbrev "MO", - :zip "64067"} - {:lat 32.4329092, - :lon -90.657001, - :house-number "2307-3099", - :street "Davis Road", - :city "Vicksburg", - :state-abbrev "MS", - :zip "39183"} - {:lat 40.9060745, - :lon -87.415645, - :house-number "6240-6298", - :street "South 125 West", - :city "Brook", - :state-abbrev "IN", - :zip "47922"} - {:lat 33.5161402, - :lon -93.74224780000002, - :house-number "598", - :street "County Road 155", - :city "Fulton", - :state-abbrev "AR", - :zip "71838"} - {:lat 31.701504, - :lon -98.761461, - :house-number "1150", - :street "Farm to Market 590", - :city "Zephyr", - :state-abbrev "TX", - :zip "76890"} - {:lat 26.2982279, - :lon -97.6897418, - :house-number "17789-18511", - :street "Briggs Coleman Road", - :city "Harlingen", - :state-abbrev "TX", - :zip "78550"} - {:lat 42.088683, - :lon -91.975566, - :house-number "6425", - :street "27th Avenue", - :city "Vinton", - :state-abbrev "IA", - :zip "52349"} - {:lat 32.4656338, - :lon -99.8982004, - :house-number "7092", - :street "Interstate 20", - :city "Merkel", - :state-abbrev "TX", - :zip "79536"} - {:lat 40.3663465, - :lon -98.64840679999999, - :house-number "16800-17998", - :street "South Holstein Avenue", - :city "Holstein", - :state-abbrev "NE", - :zip "68950"} - {:lat 31.660927, - :lon -93.00252189999999, - :house-number "4463", - :street "Louisiana 494", - :city "Natchez", - :state-abbrev "LA", - :zip "71456"} - {:lat 38.800619, - :lon -95.31160899999999, - :house-number "424", - :street "East 1000 Road", - :city "Baldwin City", - :state-abbrev "KS", - :zip "66006"} - {:lat 38.693861, - :lon -86.54561, - :house-number "747", - :street "Liberty Church Road", - :city "Mitchell", - :state-abbrev "IN", - :zip "47446"} - {:lat 41.0578547, - :lon -92.54173519999999, - :house-number "11988", - :street "198th Avenue", - :city "Ottumwa", - :state-abbrev "IA", - :zip "52501"} - {:lat 29.3565161, - :lon -95.4306926, - :house-number "2247", - :street "East Fm 1462 Road", - :city "Rosharon", - :state-abbrev "TX", - :zip "77583"} - {:lat 29.8013585, - :lon -97.990414, - :house-number "2872-3380", - :street "South Old Bastrop Highway", - :city "San Marcos", - :state-abbrev "TX", - :zip "78666"} - {:lat 38.4707749, - :lon -89.37418699999999, - :house-number "17752", - :street "Tree Road", - :city "Hoyleton", - :state-abbrev "IL", - :zip "62803"} - {:lat 48.7510492, - :lon -104.8423034, - :house-number "306", - :street "Bolster Road", - :city "Plentywood", - :state-abbrev "MT", - :zip "59254"} - {:lat 43.5180785, - :lon -92.3385188, - :house-number "16037", - :street "110th Street", - :city "Le Roy", - :state-abbrev "MN", - :zip "55951"} - {:lat 41.8676153, - :lon -77.7600223, - :house-number "1820", - :street "Fox Hill Road", - :city "Ulysses", - :state-abbrev "PA", - :zip "16948"} - {:lat 44.7527495, - :lon -93.4898975, - :house-number "2528", - :street "Wood Duck Trail", - :city "Shakopee", - :state-abbrev "MN", - :zip "55379"} - {:lat 30.121331, - :lon -95.752162, - :house-number "25119", - :street "Sea Turtle Lane", - :city "Magnolia", - :state-abbrev "TX", - :zip "77355"} - {:lat 35.47772250000001, - :lon -77.0751494, - :house-number "266", - :street "Warren Chapel Church Road", - :city "Chocowinity", - :state-abbrev "NC", - :zip "27817"} - {:lat 44.4914681, - :lon -116.3766215, - :house-number "2216", - :street "Little Weiser River Road", - :city "Indian Valley", - :state-abbrev "ID", - :zip "83632"} - {:lat 36.3622857, - :lon -80.5073991, - :house-number "498", - :street "Whitaker Road", - :city "Pinnacle", - :state-abbrev "NC", - :zip "27043"} - {:lat 47.0520143, - :lon -93.22411190000001, - :house-number "11719", - :street "County Road 424", - :city "Swan River", - :state-abbrev "MN", - :zip "55784"} - {:lat 47.9614769, - :lon -101.3832517, - :house-number "26200", - :street "62nd Street Southwest", - :city "Douglas", - :state-abbrev "ND", - :zip "58735"} - {:lat 42.99556279999999, - :lon -77.628265, - :house-number "2262-2286", - :street "Rush Mendon Road", - :city "Rush", - :state-abbrev "NY", - :zip "14543"} - {:lat 43.9160787, - :lon -99.1229738, - :house-number "23918", - :street "353 Avenue", - :city "Pukwana", - :state-abbrev "SD", - :zip "57370"} - {:lat 33.2159641, - :lon -92.2871031, - :house-number "100-172", - :street "Christian Road", - :city "Strong", - :state-abbrev "AR", - :zip "71765"} - {:lat 42.650035, - :lon -73.77100100000001, - :house-number "65", - :street "Warren Avenue", - :city "Albany", - :state-abbrev "NY", - :zip "12203"} - {:lat 44.394321, - :lon -102.7478546, - :house-number "16555", - :street "Hope Road", - :city "New Underwood", - :state-abbrev "SD", - :zip "57761"} - {:lat 42.9113731, - :lon -92.19915350000001, - :house-number "3052-3098", - :street "Ridgeway Avenue", - :city "Fredericksburg", - :state-abbrev "IA", - :zip "50630"} - {:lat 40.8734659, - :lon -79.0551088, - :house-number "2082-2474", - :street "Beaver Drive", - :city "Rochester Mills", - :state-abbrev "PA", - :zip "15771"} - {:lat 43.3070802, - :lon -76.72386759999999, - :house-number "8569-8619", - :street "Blind Sodus Bay Road", - :city "Red Creek", - :state-abbrev "NY", - :zip "13143"} - {:lat 30.3173614, - :lon -98.62123969999999, - :house-number "1902", - :street "North Grape Creek Road", - :city "Fredericksburg", - :state-abbrev "TX", - :zip "78624"} - {:lat 33.8240855, - :lon -87.2232792, - :house-number "4230", - :street "Old Birmingham Highway", - :city "Jasper", - :state-abbrev "AL", - :zip "35501"} - {:lat 43.0468514, - :lon -96.9233312, - :house-number "29906-29980", - :street "464th Avenue", - :city "Centerville", - :state-abbrev "SD", - :zip "57014"} - {:lat 37.172418, - :lon -79.618302, - :house-number "1651", - :street "White House Road", - :city "Moneta", - :state-abbrev "VA", - :zip "24121"} - {:lat 39.759614, - :lon -79.951199, - :house-number "267", - :street "Holbert Stretch", - :city "Dilliner", - :state-abbrev "PA", - :zip "15327"} - {:lat 34.1455863, - :lon -90.8787686, - :house-number "516", - :street "Owens Road", - :city "Clarksdale", - :state-abbrev "MS", - :zip "38614"} - {:lat 43.8035213, - :lon -91.9421715, - :house-number "35552", - :street "Flag Road", - :city "Lanesboro", - :state-abbrev "MN", - :zip "55949"} - {:lat 32.966564, - :lon -114.461542, - :house-number "10882", - :street "Fishers Landing Road", - :city "Yuma", - :state-abbrev "AZ", - :zip "85365"} - {:lat 46.9988408, - :lon -118.726044, - :house-number "11", - :street "Roloff Road", - :city "Lind", - :state-abbrev "WA", - :zip "99341"} - {:lat 66.9746522, - :lon -160.4243696, - :house-number "9", - :street "Airport Road", - :city "Kiana", - :state-abbrev "AK", - :zip "99749"} - {:lat 39.287272, - :lon -122.999079, - :house-number "17625", - :street "Elk Mountain Road", - :city "Upper Lake", - :state-abbrev "CA", - :zip "95485"} - {:lat 43.9419724, - :lon -85.5025347, - :house-number "8500-8846", - :street "South 210th Avenue", - :city "Reed City", - :state-abbrev "MI", - :zip "49677"} - {:lat 38.5122, - :lon -95.17989999999999, - :house-number "1780", - :street "Oregon Terrace", - :city "Rantoul", - :state-abbrev "KS", - :zip "66079"} - {:lat 42.1665281, - :lon -94.2723487, - :house-number "1944", - :street "130th Street", - :city "Paton", - :state-abbrev "IA", - :zip "50217"} - {:lat 41.626379, - :lon -97.7899607, - :house-number "51020", - :street "400 Street", - :city "Saint Edward", - :state-abbrev "NE", - :zip "68660"} - {:lat 44.5120082, - :lon -85.3419543, - :house-number "11500-11998", - :street "East No 2 Road", - :city "Fife Lake", - :state-abbrev "MI", - :zip "49633"} - {:lat 41.119998, - :lon -93.193904, - :house-number "55122", - :street "290th Avenue", - :city "Chariton", - :state-abbrev "IA", - :zip "50049"} - {:lat 37.9156339, - :lon -106.0527209, - :house-number "15206", - :street "US Highway 285", - :city "Saguache", - :state-abbrev "CO", - :zip "81149"} - {:lat 30.6939267, - :lon -93.17928409999999, - :house-number "3358-3366", - :street "Harrington Drive", - :city "DeRidder", - :state-abbrev "LA", - :zip "70634"} - {:lat 29.3688762, - :lon -99.7113421, - :house-number "4391", - :street "FM 2690", - :city "Uvalde", - :state-abbrev "TX", - :zip "78801"} - {:lat 44.1423819, - :lon -83.6578768, - :house-number "3598-3608", - :street "Turner Road", - :city "Turner", - :state-abbrev "MI", - :zip "48765"} - {:lat 30.1235445, - :lon -97.8063278, - :house-number "1200", - :street "Estancia Parkway", - :city "Austin", - :state-abbrev "TX", - :zip "78748"} - {:lat 41.7180964, - :lon -80.10770459999999, - :house-number "21794", - :street "Erie Street", - :city "Saegertown", - :state-abbrev "PA", - :zip "16433"} - {:lat 44.1819768, - :lon -94.273006, - :house-number "49846", - :street "222nd Street", - :city "Lake Crystal", - :state-abbrev "MN", - :zip "56055"} - {:lat 40.5394955, - :lon -87.9628141, - :house-number "867", - :street "East 400 North Road", - :city "Cissna Park", - :state-abbrev "IL", - :zip "60924"} - {:lat 32.292281, - :lon -98.3573976, - :house-number "2283", - :street "County Road 406", - :city "Stephenville", - :state-abbrev "TX", - :zip "76401"} - {:lat 44.293816, - :lon -72.322372, - :house-number "6490", - :street "Route 232", - :city "Marshfield", - :state-abbrev "VT", - :zip "05658"} - {:lat 33.0120157, - :lon -80.9379314, - :house-number "3614", - :street "Willow Swamp Road", - :city "Islandton", - :state-abbrev "SC", - :zip "29929"} - {:lat 43.281389, - :lon -75.67327399999999, - :house-number "3180", - :street "Wheeler Hill Road", - :city "Blossvale", - :state-abbrev "NY", - :zip "13308"} - {:lat 47.2294814, - :lon -94.9404157, - :house-number "39001-39999", - :street "205th Avenue", - :city "Laporte", - :state-abbrev "MN", - :zip "56461"} - {:lat 60.0127269, - :lon -151.4986941, - :house-number "60648", - :street "Oil Well Road", - :city "Ninilchik", - :state-abbrev "AK", - :zip "99639"} - {:lat 39.3224735, - :lon -103.4050819, - :house-number "56251", - :street "County Road 36", - :city "Genoa", - :state-abbrev "CO", - :zip "80818"} - {:lat 48.9662213, - :lon -100.7398374, - :house-number "137-199", - :street "107th Street Northwest", - :city "Souris", - :state-abbrev "ND", - :zip "58783"} - {:lat 33.4579417, - :lon -81.967394, - :house-number "1228", - :street "Gordon Highway", - :city "Augusta", - :state-abbrev "GA", - :zip "30901"} - {:lat 36.2712265, - :lon -94.7352872, - :house-number "15200", - :street "State Highway 116", - :city "Colcord", - :state-abbrev "OK", - :zip "74338"} - {:lat 30.9326095, - :lon -89.566231, - :house-number "1147-1149", - :street "Stanford Lake Road", - :city "Poplarville", - :state-abbrev "MS", - :zip "39470"} - {:lat 37.2913799, - :lon -93.56379299999999, - :house-number "13140", - :street "West Farm Road 84", - :city "Ash Grove", - :state-abbrev "MO", - :zip "65604"} - {:lat 42.1301637, - :lon -95.9202078, - :house-number "15652", - :street "County Highway L20", - :city "Castana", - :state-abbrev "IA", - :zip "51010"} - {:lat 44.8608596, - :lon -85.662477, - :house-number "9331", - :street "East Summer Field Drive", - :city "Traverse City", - :state-abbrev "MI", - :zip "49684"} - {:lat 40.7873348, - :lon -98.3402758, - :house-number "9695-9999", - :street "South Locust Street", - :city "Doniphan", - :state-abbrev "NE", - :zip "68832"} - {:lat 37.1774093, - :lon -78.1800899, - :house-number "2499", - :street "Schutt Road", - :city "Burkeville", - :state-abbrev "VA", - :zip "23922"} - {:lat 38.0326862, - :lon -92.47617, - :house-number "344", - :street "Bentown Ridge Road", - :city "Brumley", - :state-abbrev "MO", - :zip "65017"} - {:lat 53.8940388, - :lon -166.5425726, - :house-number "104", - :street "Airport Drive", - :city "Unalaska", - :state-abbrev "AK", - :zip "99692"} - {:lat 39.981358, - :lon -122.273938, - :house-number "20520", - :street "No Name Road", - :city "Corning", - :state-abbrev "CA", - :zip "96021"} - {:lat 61.7038908, - :lon -150.1429048, - :house-number "27515", - :street "West Beryozova Drive", - :city "Willow", - :state-abbrev "AK", - :zip "99688"} - {:lat 42.0000164, - :lon -101.7142596, - :house-number "43863", - :street "Nebraska 2", - :city "Hyannis", - :state-abbrev "NE", - :zip "69350"} - {:lat 42.1885791, - :lon -84.3863756, - :house-number "1005", - :street "Floyd Avenue", - :city "Jackson", - :state-abbrev "MI", - :zip "49203"} - {:lat 37.4040114, - :lon -120.8017099, - :house-number "17315", - :street "Bloss Avenue", - :city "Delhi", - :state-abbrev "CA", - :zip "95315"} - {:lat 30.069701, - :lon -96.276923, - :house-number "11235", - :street "Jones Wilke Road", - :city "Chappell Hill", - :state-abbrev "TX", - :zip "77426"} - {:lat 41.2071017, - :lon -102.8070772, - :house-number "13001-13199", - :street "Road 30", - :city "Sidney", - :state-abbrev "NE", - :zip "69162"} - {:lat 41.291868, - :lon -85.694082, - :house-number "19", - :street "EMS B4 Lane", - :city "Pierceton", - :state-abbrev "IN", - :zip "46562"} - {:lat 41.1399049, - :lon -90.6578637, - :house-number "2151-2297", - :street "50th Avenue", - :city "Aledo", - :state-abbrev "IL", - :zip "61231"} - {:lat 37.814004, - :lon -96.97592399999999, - :house-number "7250", - :street "Southwest 10th Street", - :city "Towanda", - :state-abbrev "KS", - :zip "67144"} - {:lat 34.4809811, - :lon -89.04508229999999, - :house-number "1040", - :street "Bratton Road", - :city "New Albany", - :state-abbrev "MS", - :zip "38652"} - {:lat 30.08156009999999, - :lon -94.2732719, - :house-number "555", - :street "Sandringham", - :city "Beaumont", - :state-abbrev "TX", - :zip "77713"} - {:lat 40.6779436, - :lon -90.31499459999999, - :house-number "6503-6725", - :street "East Curve Road", - :city "Avon", - :state-abbrev "IL", - :zip "61415"} - {:lat 33.48275040000001, - :lon -113.1013913, - :house-number "3451", - :street "North 491st Avenue", - :city "Tonopah", - :state-abbrev "AZ", - :zip "85354"} - {:lat 42.811637, - :lon -93.4376149, - :house-number "1855", - :street "Dogwood Avenue", - :city "Alexander", - :state-abbrev "IA", - :zip "50420"} - {:lat 31.5672052, - :lon -93.5244615, - :house-number "1841", - :street "Shuteye Road", - :city "Many", - :state-abbrev "LA", - :zip "71449"} - {:lat 38.6181693, - :lon -92.1925908, - :house-number "12209", - :street "Renz Farm Road", - :city "Holts Summit", - :state-abbrev "MO", - :zip "65043"} - {:lat 42.3862177, - :lon -99.88273369999999, - :house-number "86683", - :street "Nebraska 7", - :city "Ainsworth", - :state-abbrev "NE", - :zip "69210"} - {:lat 34.39174879999999, - :lon -80.72494530000002, - :house-number "2301-2307", - :street "State Road S-28-697", - :city "Camden", - :state-abbrev "SC", - :zip "29020"} - {:lat 39.3701702, - :lon -105.3760011, - :house-number "19519-19527", - :street "Eos Mill Road", - :city "Pine", - :state-abbrev "CO", - :zip "80470"} - {:lat 48.5438796, - :lon -98.8271975, - :house-number "8216-8298", - :street "Cavalier-Ramsey County Line Road", - :city "Starkweather", - :state-abbrev "ND", - :zip "58377"} - {:lat 38.9132154, - :lon -105.7031712, - :house-number "26800-27176", - :street "Colorado 9", - :city "Guffey", - :state-abbrev "CO", - :zip "80820"} - {:lat 30.73168429999999, - :lon -83.488559, - :house-number "5314", - :street "Dewey Road", - :city "Valdosta", - :state-abbrev "GA", - :zip "31601"} - {:lat 42.940744, - :lon -77.56138399999999, - :house-number "1855", - :street "Hickory Lane", - :city "Honeoye Falls", - :state-abbrev "NY", - :zip "14472"} - {:lat 38.889392, - :lon -95.732744, - :house-number "4501", - :street "Southwest 97th Street", - :city "Wakarusa", - :state-abbrev "KS", - :zip "66546"} - {:lat 45.238332, - :lon -115.8133813, - :house-number "24833", - :street "Warren Wagon Road", - :city "McCall", - :state-abbrev "ID", - :zip "83638"} - {:lat 36.492435, - :lon -86.966335, - :house-number "3724", - :street "Hoods Branch Road", - :city "Springfield", - :state-abbrev "TN", - :zip "37172"} - {:lat 32.9092579, - :lon -103.2659859, - :house-number "24", - :street "Rosebud Lane", - :city "Lovington", - :state-abbrev "NM", - :zip "88260"} - {:lat 34.7794615, - :lon -89.2971718, - :house-number "3535", - :street "Little Snow Creek Road", - :city "Holly Springs", - :state-abbrev "MS", - :zip "38635"} - {:lat 40.9873587, - :lon -77.5623052, - :house-number "246", - :street "Hoy Road", - :city "Howard", - :state-abbrev "PA", - :zip "16841"} - {:lat 48.5014899, - :lon -122.1953009, - :house-number "25502", - :street "Hoehn Road", - :city "Sedro-Woolley", - :state-abbrev "WA", - :zip "98284"} - {:lat 36.1745853, - :lon -83.1806323, - :house-number "4142-4198", - :street "Parrottsville Road", - :city "Bybee", - :state-abbrev "TN", - :zip "37713"} - {:lat 44.0434558, - :lon -92.8169403, - :house-number "21301-21361", - :street "625th Street", - :city "Dodge Center", - :state-abbrev "MN", - :zip "55927"} - {:lat 31.8284709, - :lon -86.6683085, - :house-number "423", - :street "Manningham Loop", - :city "Greenville", - :state-abbrev "AL", - :zip "36037"} - {:lat 30.79443929999999, - :lon -96.29737539999999, - :house-number "9520", - :street "Dilly Shaw Tap Road", - :city "Bryan", - :state-abbrev "TX", - :zip "77808"} - {:lat 42.979323, - :lon -73.958883, - :house-number "1417", - :street "Peaceable Street", - :city "Ballston Spa", - :state-abbrev "NY", - :zip "12020"} - {:lat 33.6856683, - :lon -96.77413949999999, - :house-number "4084", - :street "Old Southmayd Road", - :city "Sherman", - :state-abbrev "TX", - :zip "75092"} - {:lat 40.5598283, - :lon -87.73477539999999, - :house-number "2001-2099", - :street "County Road 500 North", - :city "Wellington", - :state-abbrev "IL", - :zip "60973"} - {:lat 33.6263821, - :lon -91.4221505, - :house-number "308", - :street "Baughman Road", - :city "McGehee", - :state-abbrev "AR", - :zip "71654"} - {:lat 41.367849, - :lon -73.1448755, - :house-number "5", - :street "Sunset Terrace", - :city "Seymour", - :state-abbrev "CT", - :zip "06483"} - {:lat 33.2505184, - :lon -80.7431324, - :house-number "1703-1825", - :street "Banbury Drive", - :city "Branchville", - :state-abbrev "SC", - :zip "29432"} - {:lat 39.614977, - :lon -84.792006, - :house-number "8774", - :street "Fairhaven College CRNR Road", - :city "College Corner", - :state-abbrev "OH", - :zip "45003"} - {:lat 29.80548, - :lon -81.580928, - :house-number "108", - :street "Creek Lane", - :city "Palatka", - :state-abbrev "FL", - :zip "32177"} - {:lat 36.6515872, - :lon -121.6238129, - :house-number "1428", - :street "Abbott Street", - :city "Salinas", - :state-abbrev "CA", - :zip "93901"} - {:lat 42.6929731, - :lon -78.0005899, - :house-number "3980", - :street "Middle Reservation Road", - :city "Perry", - :state-abbrev "NY", - :zip "14530"} - {:lat 48.536864, - :lon -109.697746, - :house-number "1335", - :street "Washington Avenue", - :city "Havre", - :state-abbrev "MT", - :zip "59501"} - {:lat 37.5975641, - :lon -79.0825886, - :house-number "134-164", - :street "Berry Hill Lane", - :city "Amherst", - :state-abbrev "VA", - :zip "24521"} - {:lat 41.17628510000001, - :lon -105.8302223, - :house-number "61", - :street "Mason Lane", - :city "Laramie", - :state-abbrev "WY", - :zip "82070"} - {:lat 45.1875366, - :lon -92.04145539999999, - :house-number "E2302", - :street "County Road V", - :city "Prairie Farm", - :state-abbrev "WI", - :zip "54762"} - {:lat 46.215794, - :lon -104.6251721, - :house-number "312", - :street "Lame Jones Trail", - :city "Plevna", - :state-abbrev "MT", - :zip "59344"} - {:lat 29.7607019, - :lon -97.7324809, - :house-number "1395", - :street "Callihan Road", - :city "Luling", - :state-abbrev "TX", - :zip "78648"} - {:lat 38.6832382, - :lon -106.9923199, - :house-number "7911", - :street "County Road 730", - :city "Gunnison", - :state-abbrev "CO", - :zip "81230"} - {:lat 42.065906, - :lon -91.503693, - :house-number "2818", - :street "Jordans Grove Road", - :city "Marion", - :state-abbrev "IA", - :zip "52302"} - {:lat 34.0908609, - :lon -116.269298, - :house-number "9000", - :street "Rock Haven Road", - :city "Joshua Tree", - :state-abbrev "CA", - :zip "92252"} - {:lat 37.1489336, - :lon -100.1449543, - :house-number "23000-23524", - :street "29 Road", - :city "Meade", - :state-abbrev "KS", - :zip "67864"} - {:lat 40.830092, - :lon -78.328512, - :house-number "1512", - :street "Spring Street", - :city "Houtzdale", - :state-abbrev "PA", - :zip "16651"} - {:lat 40.8326643, - :lon -102.0270649, - :house-number "75915", - :street "Road 312", - :city "Venango", - :state-abbrev "NE", - :zip "69168"} - {:lat 31.2528411, - :lon -93.4338884, - :house-number "230-240", - :street "Farris Cemetary Road", - :city "Anacoco", - :state-abbrev "LA", - :zip "71403"} - {:lat 43.1328381, - :lon -74.11540099999999, - :house-number "551", - :street "Fayville Road", - :city "Broadalbin", - :state-abbrev "NY", - :zip "12025"} - {:lat 61.9880241, - :lon -147.0136133, - :house-number "8110", - :street "Glenn Highway", - :city "Glennallen", - :state-abbrev "AK", - :zip "99588"} - {:lat 30.5900209, - :lon -104.0698574, - :house-number "218", - :street "Cedar Trail", - :city "Fort Davis", - :state-abbrev "TX", - :zip "79734"} - {:lat 37.0965383, - :lon -86.13427329999999, - :house-number "19366", - :street "Louisville Road", - :city "Park City", - :state-abbrev "KY", - :zip "42160"} - {:lat 41.5211048, - :lon -79.9952654, - :house-number "25940-27498", - :street "Stockton Corners Road", - :city "Cochranton", - :state-abbrev "PA", - :zip "16314"} - {:lat 28.913617, - :lon -95.97610309999999, - :house-number "60", - :street "Avenue F North", - :city "Bay City", - :state-abbrev "TX", - :zip "77414"} - {:lat 47.4792833, - :lon -98.7133154, - :house-number "401-499", - :street "86th Avenue Northeast", - :city "Glenfield", - :state-abbrev "ND", - :zip "58443"} - {:lat 46.9018564, - :lon -117.7551321, - :house-number "2551", - :street "Storment Road", - :city "Endicott", - :state-abbrev "WA", - :zip "99125"} - {:lat 41.1757901, - :lon -86.3298392, - :house-number "14036", - :street "Indiana 110", - :city "Rochester", - :state-abbrev "IN", - :zip "46975"} - {:lat 43.101427, - :lon -78.2494959, - :house-number "6444", - :street "Fisher Road", - :city "Oakfield", - :state-abbrev "NY", - :zip "14125"} - {:lat 48.6786822, - :lon -113.8184402, - :house-number "16809", - :street "Going-to-the-Sun Road", - :city "West Glacier", - :state-abbrev "MT", - :zip "59936"} - {:lat 32.025204, - :lon -86.6599078, - :house-number "5187", - :street "County Road 45", - :city "Fort Deposit", - :state-abbrev "AL", - :zip "36032"} - {:lat 43.2662019, - :lon -96.19779609999999, - :house-number "2601-2699", - :street "Grant Avenue", - :city "Doon", - :state-abbrev "IA", - :zip "51235"} - {:lat 46.2787702, - :lon -111.2326623, - :house-number "92", - :street "Upper Greyson Creek Road", - :city "Townsend", - :state-abbrev "MT", - :zip "59644"} - {:lat 46.5612182, - :lon -117.5284755, - :house-number "140", - :street "South Deadman Road", - :city "Pomeroy", - :state-abbrev "WA", - :zip "99347"} - {:lat 38.5058074, - :lon -88.7177379, - :house-number "818-898", - :street "County Road 2300 East", - :city "Iuka", - :state-abbrev "IL", - :zip "62849"} - {:lat 30.9197909, - :lon -94.1673134, - :house-number "1898", - :street "County Road 027", - :city "Jasper", - :state-abbrev "TX", - :zip "75951"} - {:lat 38.8271655, - :lon -98.4199407, - :house-number "411-467", - :street "Avenue D", - :city "Wilson", - :state-abbrev "KS", - :zip "67490"} - {:lat 43.2543472, - :lon -94.0150718, - :house-number "2208", - :street "340th Street", - :city "Titonka", - :state-abbrev "IA", - :zip "50480"} - {:lat 33.1439027, - :lon -94.74486329999999, - :house-number "1564", - :street "County Road 3309", - :city "Omaha", - :state-abbrev "TX", - :zip "75571"} - {:lat 47.459075, - :lon -112.8400399, - :house-number "2335", - :street "Mule Creek Road", - :city "Augusta", - :state-abbrev "MT", - :zip "59410"} - {:lat 39.0120209, - :lon -83.81941479999999, - :house-number "13615-13827", - :street "Matthews Road", - :city "Sardinia", - :state-abbrev "OH", - :zip "45171"} - {:lat 37.990355, - :lon -85.4719301, - :house-number "3900", - :street "Love Lane", - :city "Coxs Creek", - :state-abbrev "KY", - :zip "40013"} - {:lat 30.78410239999999, - :lon -96.24686849999999, - :house-number "11459", - :street "Oak Lake Road", - :city "Bryan", - :state-abbrev "TX", - :zip "77808"} - {:lat 37.570637, - :lon -119.978165, - :house-number "5300", - :street "Rumley Mine Road", - :city "Midpines", - :state-abbrev "CA", - :zip "95345"} - {:lat 35.3526971, - :lon -97.10998529999999, - :house-number "14508", - :street "North South 331", - :city "Shawnee", - :state-abbrev "OK", - :zip "74801"} - {:lat 30.557133, - :lon -97.37338349999999, - :house-number "7197-7805", - :street "Farm to Market Road 619", - :city "Taylor", - :state-abbrev "TX", - :zip "76574"} - {:lat 41.6179294, - :lon -93.46465049999999, - :house-number "2627-2687", - :street "Northeast 72nd Street", - :city "Altoona", - :state-abbrev "IA", - :zip "50009"} - {:lat 35.025246, - :lon -89.559371, - :house-number "1437", - :street "Knox Road", - :city "Rossville", - :state-abbrev "TN", - :zip "38066"} - {:lat 41.86593149999999, - :lon -90.72437719999999, - :house-number "1896", - :street "215th Street", - :city "Grand Mound", - :state-abbrev "IA", - :zip "52751"} - {:lat 42.7013214, - :lon -97.08685129999999, - :house-number "57207", - :street "888th Road", - :city "Wynot", - :state-abbrev "NE", - :zip "68792"} - {:lat 31.184549, - :lon -89.451286, - :house-number "223", - :street "Haden Road", - :city "Purvis", - :state-abbrev "MS", - :zip "39475"} - {:lat 43.91931470000001, - :lon -95.9950034, - :house-number "351", - :street "Valley Road", - :city "Chandler", - :state-abbrev "MN", - :zip "56122"} - {:lat 41.401808, - :lon -82.141712, - :house-number "42270", - :street "Griswold Road", - :city "Elyria", - :state-abbrev "OH", - :zip "44035"} - {:lat 39.625267, - :lon -95.006856, - :house-number "12920", - :street "Southwest 81st Road", - :city "Rushville", - :state-abbrev "MO", - :zip "64484"} - {:lat 26.4487202, - :lon -81.2721499, - :house-number "2424", - :street "Thorp Road", - :city "Immokalee", - :state-abbrev "FL", - :zip "34142"} - {:lat 46.3331733, - :lon -91.53325819999999, - :house-number "52000-52086", - :street "Wisconsin 27", - :city "Solon Springs", - :state-abbrev "WI", - :zip "54873"} - {:lat 32.7212981, - :lon -114.3471867, - :house-number "7115", - :street "South Avenue 17 East", - :city "Yuma", - :state-abbrev "AZ", - :zip "85365"} - {:lat 35.492303, - :lon -78.22756799999999, - :house-number "481", - :street "Wc Braswell Road", - :city "Selma", - :state-abbrev "NC", - :zip "27576"} - {:lat 47.903819, - :lon -94.09948299999999, - :house-number "11802", - :street "South Co Road 6", - :city "Mizpah", - :state-abbrev "MN", - :zip "56660"} - {:lat 47.1241259, - :lon -111.550147, - :house-number "970", - :street "Adel Road", - :city "Cascade", - :state-abbrev "MT", - :zip "59421"} - {:lat 38.8777388, - :lon -107.1770073, - :house-number "12492-13498", - :street "County Road 12", - :city "Somerset", - :state-abbrev "CO", - :zip "81434"} - {:lat 48.4847586, - :lon -114.3989592, - :house-number "67", - :street "Eagle Creek Trail", - :city "Whitefish", - :state-abbrev "MT", - :zip "59937"} - {:lat 26.2078681, - :lon -98.3643379, - :house-number "3104", - :street "Humberto Garza Junior Street", - :city "Mission", - :state-abbrev "TX", - :zip "78572"} - {:lat 43.4984703, - :lon -84.81185909999999, - :house-number "9756", - :street "South Lincoln Road", - :city "Shepherd", - :state-abbrev "MI", - :zip "48883"} - {:lat 38.0396349, - :lon -78.313039, - :house-number "478", - :street "Campbell Road", - :city "Keswick", - :state-abbrev "VA", - :zip "22947"} - {:lat 39.4381138, - :lon -93.8289722, - :house-number "46801", - :street "East 192nd Street", - :city "Richmond", - :state-abbrev "MO", - :zip "64085"} - {:lat 43.9437577, - :lon -94.2440666, - :house-number "13478", - :street "507th Avenue", - :city "Vernon Center", - :state-abbrev "MN", - :zip "56090"} - {:lat 38.885884, - :lon -95.095698, - :house-number "1019", - :street "East 2200 Road", - :city "Eudora", - :state-abbrev "KS", - :zip "66025"} - {:lat 43.3300296, - :lon -82.53618829999999, - :house-number "3384", - :street "Old Orchard Lane", - :city "Lexington", - :state-abbrev "MI", - :zip "48450"} - {:lat 42.5477132, - :lon -85.9206291, - :house-number "3837", - :street "Forest Trail", - :city "Allegan", - :state-abbrev "MI", - :zip "49010"} - {:lat 37.3668518, - :lon -120.6647798, - :house-number "5301-5315", - :street "Olive Avenue", - :city "Atwater", - :state-abbrev "CA", - :zip "95301"} - {:lat 36.411321, - :lon -95.750602, - :house-number "13996", - :street "U.S Highway 169", - :city "Oologah", - :state-abbrev "OK", - :zip "74053"} - {:lat 39.1290762, - :lon -78.88821709999999, - :house-number "3936", - :street "Ashton Woods Drive", - :city "Moorefield", - :state-abbrev "WV", - :zip "26836"} - {:lat 43.0902834, - :lon -98.9564362, - :house-number "36150", - :street "Eldeen Avenue", - :city "Bonesteel", - :state-abbrev "SD", - :zip "57317"} - {:lat 47.150362, - :lon -120.872251, - :house-number "470", - :street "Thornton View Road", - :city "Cle Elum", - :state-abbrev "WA", - :zip "98922"} - {:lat 36.259424, - :lon -79.161563, - :house-number "1986", - :street "North Carolina 49", - :city "Prospect Hill", - :state-abbrev "NC", - :zip "27314"} - {:lat 43.3785313, - :lon -93.675874, - :house-number "42473", - :street "150th Avenue", - :city "Leland", - :state-abbrev "IA", - :zip "50453"} - {:lat 44.403199, - :lon -93.00742400000001, - :house-number "37929", - :street "20th Avenue", - :city "Dennison", - :state-abbrev "MN", - :zip "55018"} - {:lat 33.8434868, - :lon -95.90124290000001, - :house-number "1987", - :street "Farm to Market 79", - :city "Telephone", - :state-abbrev "TX", - :zip "75488"} - {:lat 38.3975096, - :lon -83.6592131, - :house-number "7219", - :street "Morehead Road", - :city "Flemingsburg", - :state-abbrev "KY", - :zip "41041"} - {:lat 39.0705916, - :lon -80.907283, - :house-number "864", - :street "Sunny Hollow Road", - :city "Smithville", - :state-abbrev "WV", - :zip "26178"} - {:lat 33.704565, - :lon -102.5888482, - :house-number "4151-4199", - :street "Kenya Road", - :city "Levelland", - :state-abbrev "TX", - :zip "79336"} - {:lat 33.472702, - :lon -82.48024939999999, - :house-number "1496-1502", - :street "Harrison Road Southeast", - :city "Thomson", - :state-abbrev "GA", - :zip "30824"} - {:lat 33.2287456, - :lon -99.195262, - :house-number "1551", - :street "U.S. 283", - :city "Throckmorton", - :state-abbrev "TX", - :zip "76483"} - {:lat 39.1898386, - :lon -93.3398946, - :house-number "27335", - :street "Durango Avenue", - :city "Malta Bend", - :state-abbrev "MO", - :zip "65339"} - {:lat 40.3536303, - :lon -97.20280249999999, - :house-number "1900", - :street "County Road Y", - :city "Western", - :state-abbrev "NE", - :zip "68464"} - {:lat 37.278886, - :lon -80.752978, - :house-number "167", - :street "Cooper Lane", - :city "Pearisburg", - :state-abbrev "VA", - :zip "24134"} - {:lat 35.1345926, - :lon -76.7618676, - :house-number "369", - :street "McCotter Road", - :city "Bayboro", - :state-abbrev "NC", - :zip "28515"} - {:lat 34.0696338, - :lon -103.2871496, - :house-number "1400-1598", - :street "South Roosevelt Road 13", - :city "Portales", - :state-abbrev "NM", - :zip "88130"} - {:lat 32.429694, - :lon -111.331505, - :house-number "12402", - :street "North Carbine Road", - :city "Marana", - :state-abbrev "AZ", - :zip "85653"} - {:lat 40.792316, - :lon -81.224113, - :house-number "9508", - :street "Lisbon Street Northeast", - :city "East Canton", - :state-abbrev "OH", - :zip "44730"} - {:lat 48.42087129999999, - :lon -98.4923376, - :house-number "6901-6963", - :street "100th Avenue Northeast", - :city "Edmore", - :state-abbrev "ND", - :zip "58330"} - {:lat 30.583725, - :lon -83.7119063, - :house-number "2-282", - :street "West 7th Way", - :city "Greenville", - :state-abbrev "FL", - :zip "32331"} - {:lat 30.714283, - :lon -94.902378, - :house-number "208", - :street "Dogwood Hill Road", - :city "Livingston", - :state-abbrev "TX", - :zip "77351"} - {:lat 45.75160400000001, - :lon -108.604186, - :house-number "22", - :street "Bridlewood Drive", - :city "Billings", - :state-abbrev "MT", - :zip "59102"} - {:lat 33.737525, - :lon -85.2354063, - :house-number "219", - :street "Old Ridgeway Road", - :city "Bremen", - :state-abbrev "GA", - :zip "30110"} - {:lat 46.5844548, - :lon -92.9939555, - :house-number "2612", - :street "South Finn Road", - :city "Kettle River", - :state-abbrev "MN", - :zip "55757"} - {:lat 33.8833259, - :lon -96.21635719999999, - :house-number "5499-5529", - :street "N3820 Road", - :city "Bokchito", - :state-abbrev "OK", - :zip "74726"} - {:lat 31.1658398, - :lon -91.2507991, - :house-number "498", - :street "Walker Lane", - :city "Woodville", - :state-abbrev "MS", - :zip "39669"} - {:lat 39.2245989, - :lon -97.42682169999999, - :house-number "1833-1899", - :street "North 270th Road", - :city "Clay Center", - :state-abbrev "KS", - :zip "67432"} - {:lat 38.6539478, - :lon -98.01970569999999, - :house-number "5", - :street "Bourbon Street", - :city "Geneseo", - :state-abbrev "KS", - :zip "67444"} - {:lat 47.41297850000001, - :lon -105.7740833, - :house-number "844-878", - :street "Mayberry Road", - :city "Circle", - :state-abbrev "MT", - :zip "59215"} - {:lat 46.8850376, - :lon -102.5623717, - :house-number "3639", - :street "100th Avenue Southwest", - :city "Gladstone", - :state-abbrev "ND", - :zip "58630"} - {:lat 38.329125, - :lon -82.6987683, - :house-number "16800-17498", - :street "Country Club Drive", - :city "Catlettsburg", - :state-abbrev "KY", - :zip "41129"} - {:lat 41.3518892, - :lon -82.8445302, - :house-number "500-550", - :street "Southwest Road", - :city "Castalia", - :state-abbrev "OH", - :zip "44824"} - {:lat 38.294071, - :lon -120.601589, - :house-number "15469", - :street "Jesus Maria Road", - :city "Mokelumne Hill", - :state-abbrev "CA", - :zip "95245"} - {:lat 33.9167659, - :lon -94.38419499999999, - :house-number "160", - :street "Riverview Drive", - :city "Horatio", - :state-abbrev "AR", - :zip "71842"} - {:lat 35.472722, - :lon -81.805212, - :house-number "198", - :street "Pearson Moss Drive", - :city "Bostic", - :state-abbrev "NC", - :zip "28018"} - {:lat 42.1307055, - :lon -102.4795368, - :house-number "3466", - :street "183rd Trail", - :city "Lakeside", - :state-abbrev "NE", - :zip "69351"} - {:lat 25.8698057, - :lon -81.173395, - :house-number "47201", - :street "Tamiami Trail East", - :city "Ochopee", - :state-abbrev "FL", - :zip "34141"} - {:lat 44.696725, - :lon -107.214077, - :house-number "37", - :street "Beckton Drive", - :city "Sheridan", - :state-abbrev "WY", - :zip "82801"} - {:lat 39.1864188, - :lon -105.4994581, - :house-number "25800", - :street "County Road 77", - :city "Jefferson", - :state-abbrev "CO", - :zip "80456"} - {:lat 47.0808663, - :lon -98.8165657, - :house-number "7901-7999", - :street "23rd Street Southeast", - :city "Buchanan", - :state-abbrev "ND", - :zip "58420"} - {:lat 37.0223516, - :lon -120.890925, - :house-number "18361-18655", - :street "South Creek Road", - :city "Los Banos", - :state-abbrev "CA", - :zip "93635"} - {:lat 48.37040150000001, - :lon -98.93015109999999, - :house-number "7740-7798", - :street "66th Street Northeast", - :city "Webster", - :state-abbrev "ND", - :zip "58382"} - {:lat 32.8409626, - :lon -81.20136839999999, - :house-number "5016", - :street "Luray Highway", - :city "Brunson", - :state-abbrev "SC", - :zip "29911"} - {:lat 42.4575898, - :lon -84.0450789, - :house-number "15313", - :street "Livermore Road", - :city "Pinckney", - :state-abbrev "MI", - :zip "48169"} - {:lat 41.7911848, - :lon -86.11495819999999, - :house-number "3205-3209", - :street "U.S. 12", - :city "Niles", - :state-abbrev "MI", - :zip "49120"} - {:lat 42.4367078, - :lon -71.8534573, - :house-number "70", - :street "Coal Kiln Road", - :city "Princeton", - :state-abbrev "MA", - :zip "01541"} - {:lat 44.3916327, - :lon -120.93806, - :house-number "7337", - :street "Northwest Ryegrass Road", - :city "Prineville", - :state-abbrev "OR", - :zip "97754"} - {:lat 32.885575, - :lon -94.810548, - :house-number "12231", - :street "Farm to Market Road 2796", - :city "Pittsburg", - :state-abbrev "TX", - :zip "75686"} - {:lat 36.012014, - :lon -96.559501, - :house-number "10431", - :street "South 513th West Avenue", - :city "Drumright", - :state-abbrev "OK", - :zip "74030"} - {:lat 31.9861377, - :lon -83.30078189999999, - :house-number "470", - :street "South Broad Street", - :city "Abbeville", - :state-abbrev "GA", - :zip "31001"} - {:lat 37.67874, - :lon -120.195549, - :house-number "4695", - :street "Crown Lead Road", - :city "Coulterville", - :state-abbrev "CA", - :zip "95311"} - {:lat 48.026764, - :lon -114.5130826, - :house-number "2734", - :street "Browns Meadow Road", - :city "Kila", - :state-abbrev "MT", - :zip "59920"} - {:lat 47.6827165, - :lon -118.234703, - :house-number "28515", - :street "Horwege Road", - :city "Davenport", - :state-abbrev "WA", - :zip "99122"} - {:lat 44.8173148, - :lon -113.6845403, - :house-number "542", - :street "National Forest Development Road 008", - :city "Lemhi", - :state-abbrev "ID", - :zip "83465"} - {:lat 35.22018, - :lon -82.092621, - :house-number "653", - :street "Green Fields Lane", - :city "Columbus", - :state-abbrev "NC", - :zip "28722"} - {:lat 36.8504259, - :lon -87.9559804, - :house-number "6627", - :street "Blue Spring Road", - :city "Cadiz", - :state-abbrev "KY", - :zip "42211"} - {:lat 46.77222, - :lon -104.71361, - :house-number "315", - :street "Pine Unit Road", - :city "Fallon", - :state-abbrev "MT", - :zip "59326"} - {:lat 45.8193559, - :lon -120.4218159, - :house-number "353-551", - :street "Newell Grade Road", - :city "Roosevelt", - :state-abbrev "WA", - :zip "99356"} - {:lat 41.1167214, - :lon -95.33849479999999, - :house-number "1224-1298", - :street "130th Street", - :city "Emerson", - :state-abbrev "IA", - :zip "51533"} - {:lat 32.0650555, - :lon -85.70541229999999, - :house-number "3686", - :street "County Road 31", - :city "Union Springs", - :state-abbrev "AL", - :zip "36089"} - {:lat 43.9630749, - :lon -99.5404085, - :house-number "33151", - :street "South Dakota 47", - :city "Reliance", - :state-abbrev "SD", - :zip "57569"} - {:lat 43.1264718, - :lon -74.08001809999999, - :house-number "139", - :street "Hans Creek Road", - :city "Broadalbin", - :state-abbrev "NY", - :zip "12025"} - {:lat 35.5942876, - :lon -77.098241, - :house-number "360", - :street "Page Road", - :city "Washington", - :state-abbrev "NC", - :zip "27889"} - {:lat 39.835329, - :lon -123.130293, - :house-number "30374", - :street "Mendocino Pass Road", - :city "Covelo", - :state-abbrev "CA", - :zip "95428"} - {:lat 44.0337702, - :lon -90.0817086, - :house-number "700-708", - :street "West Middle Street", - :city "Necedah", - :state-abbrev "WI", - :zip "54646"} - {:lat 44.2286948, - :lon -69.62147089999999, - :house-number "51", - :street "Gorman Lane", - :city "Whitefield", - :state-abbrev "ME", - :zip "04353"} - {:lat 47.8437113, - :lon -96.7390394, - :house-number "22364", - :street "320th Avenue Southwest", - :city "Fisher", - :state-abbrev "MN", - :zip "56723"} - {:lat 34.054148, - :lon -83.75065699999999, - :house-number "546", - :street "Mulberry Road", - :city "Winder", - :state-abbrev "GA", - :zip "30680"} - {:lat 40.8422394, - :lon -86.446347, - :house-number "5001-5637", - :street "North Co Road 375 West", - :city "Royal Center", - :state-abbrev "IN", - :zip "46978"} - {:lat 40.7876069, - :lon -91.96811699999999, - :house-number "17736-18032", - :street "Lark Avenue", - :city "Keosauqua", - :state-abbrev "IA", - :zip "52565"} - {:lat 45.7504108, - :lon -86.91291009999999, - :house-number "8905", - :street "15th Road", - :city "Rapid River", - :state-abbrev "MI", - :zip "49878"} - {:lat 46.9343702, - :lon -102.8765487, - :house-number "11523", - :street "33rd Street Southwest", - :city "Dickinson", - :state-abbrev "ND", - :zip "58601"} - {:lat 39.71045609999999, - :lon -90.60765669999999, - :house-number "225-299", - :street "West Phillips Ferry Road", - :city "Bluffs", - :state-abbrev "IL", - :zip "62621"} - {:lat 42.6513994, - :lon -114.8666274, - :house-number "901-921", - :street "East 4500 North", - :city "Buhl", - :state-abbrev "ID", - :zip "83316"} - {:lat 39.593326, - :lon -106.0200552, - :house-number "51-99", - :street "Circle B", - :city "Dillon", - :state-abbrev "CO", - :zip "80435"} - {:lat 43.863401, - :lon -88.6968, - :house-number "W10733", - :street "Olden Road", - :city "Pickett", - :state-abbrev "WI", - :zip "54964"} - {:lat 38.8216915, - :lon -81.3836443, - :house-number "1501-1517", - :street "Parkersburg Road", - :city "Spencer", - :state-abbrev "WV", - :zip "25276"} - {:lat 58.2438, - :lon -134.296, - :house-number "6020", - :street "Thane Road", - :city "Juneau", - :state-abbrev "AK", - :zip "99801"} - {:lat 43.150971, - :lon -78.580462, - :house-number "7922", - :street "Lincoln Avenue Extension", - :city "Lockport", - :state-abbrev "NY", - :zip "14094"} - {:lat 35.770582, - :lon -84.92956099999999, - :house-number "1100", - :street "Happy Top Road", - :city "Grandview", - :state-abbrev "TN", - :zip "37337"} - {:lat 38.3971132, - :lon -84.7471047, - :house-number "80", - :street "Old Teresita Road", - :city "Owenton", - :state-abbrev "KY", - :zip "40359"} - {:lat 33.2654697, - :lon -96.30465389999999, - :house-number "7667", - :street "County Road 705", - :city "Farmersville", - :state-abbrev "TX", - :zip "75442"} - {:lat 37.7642515, - :lon -106.7990794, - :house-number "36", - :street "Stagecoach Drive", - :city "South Fork", - :state-abbrev "CO", - :zip "81154"} - {:lat 31.8048375, - :lon -93.3722943, - :house-number "2191-2497", - :street "Allen-Beulah Road", - :city "Marthaville", - :state-abbrev "LA", - :zip "71450"} - {:lat 38.02106089999999, - :lon -102.3020413, - :house-number "26496-28658", - :street "County Road 25", - :city "Granada", - :state-abbrev "CO", - :zip "81041"} - {:lat 44.688761, - :lon -87.99036, - :house-number "4660", - :street "Brown Road", - :city "Little Suamico", - :state-abbrev "WI", - :zip "54141"} - {:lat 47.3486486, - :lon -119.9857975, - :house-number "299-373", - :street "Palisades Road", - :city "Palisades", - :state-abbrev "WA", - :zip "98845"} - {:lat 30.6165006, - :lon -102.5659586, - :house-number "185", - :street "Puckett Road", - :city "Fort Stockton", - :state-abbrev "TX", - :zip "79735"} - {:lat 41.5701556, - :lon -101.3875156, - :house-number "1796-1798", - :street "Nebraska 92", - :city "Sutherland", - :state-abbrev "NE", - :zip "69165"} - {:lat 39.8311148, - :lon -121.7066288, - :house-number "11422", - :street "Deer Creek Highway", - :city "Chico", - :state-abbrev "CA", - :zip "95928"} - {:lat 35.2979402, - :lon -80.7213334, - :house-number "1709", - :street "Jeffrey Bryan Drive", - :city "Charlotte", - :state-abbrev "NC", - :zip "28213"} - {:lat 38.9807004, - :lon -90.96349490000001, - :house-number "242-298", - :street "Thompson Drive", - :city "Troy", - :state-abbrev "MO", - :zip "63379"} - {:lat 45.1793539, - :lon -91.082781, - :house-number "29408", - :street "230th Avenue", - :city "Holcombe", - :state-abbrev "WI", - :zip "54745"} - {:lat 35.085311, - :lon -90.246799, - :house-number "1230", - :street "Caldwell Road", - :city "Proctor", - :state-abbrev "AR", - :zip "72376"} - {:lat 29.4831868, - :lon -98.8856216, - :house-number "715", - :street "County Road 2615", - :city "Rio Medina", - :state-abbrev "TX", - :zip "78066"} - {:lat 37.336518, - :lon -108.490367, - :house-number "30205", - :street "Highway 160", - :city "Cortez", - :state-abbrev "CO", - :zip "81321"} - {:lat 38.368046, - :lon -121.904845, - :house-number "6418", - :street "Byrnes Road", - :city "Vacaville", - :state-abbrev "CA", - :zip "95687"} - {:lat 37.5995954, - :lon -105.8436795, - :house-number "1619", - :street "County Road 111", - :city "Mosca", - :state-abbrev "CO", - :zip "81146"} - {:lat 44.55905389999999, - :lon -88.77040559999999, - :house-number "E9075", - :street "Bear Creek Road", - :city "Clintonville", - :state-abbrev "WI", - :zip "54929"} - {:lat 44.657691, - :lon -91.543655, - :house-number "S11780", - :street "County Road B", - :city "Eleva", - :state-abbrev "WI", - :zip "54738"} - {:lat 36.944042, - :lon -81.082864, - :house-number "485", - :street "West Union Street", - :city "Wytheville", - :state-abbrev "VA", - :zip "24382"} - {:lat 42.4936868, - :lon -91.6677739, - :house-number "2010-2054", - :street "Victor Avenue", - :city "Winthrop", - :state-abbrev "IA", - :zip "50682"} - {:lat 35.2039787, - :lon -94.7005509, - :house-number "25540", - :street "Nubbin Ridge", - :city "Spiro", - :state-abbrev "OK", - :zip "74959"} - {:lat 41.3055032, - :lon -96.20784669999999, - :house-number "18465-18849", - :street "Fort Street", - :city "Omaha", - :state-abbrev "NE", - :zip "68022"} - {:lat 41.3314761, - :lon -94.7418039, - :house-number "75848", - :street "Memphis Road", - :city "Anita", - :state-abbrev "IA", - :zip "50020"} - {:lat 63.44814479999999, - :lon -148.8350787, - :house-number "Mile 214.5", - :street "George Parks Highway", - :city "Denali National Park and Preserve", - :state-abbrev "AK", - :zip "99755"} - {:lat 41.3480212, - :lon -76.3170663, - :house-number "459", - :street "Ricketts Drive", - :city "Benton", - :state-abbrev "PA", - :zip "17814"} - {:lat 39.067987, - :lon -104.887748, - :house-number "3245", - :street "Doolittle Road", - :city "Monument", - :state-abbrev "CO", - :zip "80132"} - {:lat 38.044187, - :lon -121.448421, - :house-number "13222", - :street "West Rindge Road", - :city "Stockton", - :state-abbrev "CA", - :zip "95219"} - {:lat 43.4394989, - :lon -75.3129672, - :house-number "8724-8768", - :street "Domser Road", - :city "Boonville", - :state-abbrev "NY", - :zip "13309"} - {:lat 46.368827, - :lon -94.577142, - :house-number "12012", - :street "57th Avenue Southwest", - :city "Pillager", - :state-abbrev "MN", - :zip "56473"} - {:lat 32.3276924, - :lon -91.78834379999999, - :house-number "615", - :street "Whitehall Road", - :city "Mangham", - :state-abbrev "LA", - :zip "71259"} - {:lat 32.1274578, - :lon -86.8248954, - :house-number "19", - :street "Casey Road", - :city "Sardis", - :state-abbrev "AL", - :zip "36775"} - {:lat 46.88974, - :lon -119.6439359, - :house-number "58062", - :street "3rd Street Northeast", - :city "Royal City", - :state-abbrev "WA", - :zip "99357"} - {:lat 34.312091, - :lon -86.018813, - :house-number "2961", - :street "County Road 28", - :city "Crossville", - :state-abbrev "AL", - :zip "35962"} - {:lat 37.8714349, - :lon -92.4307795, - :house-number "19610", - :street "Highway 7", - :city "Richland", - :state-abbrev "MO", - :zip "65556"} - {:lat 42.2611518, - :lon -70.89390490000001, - :house-number "195", - :street "Downer Avenue", - :city "Hingham", - :state-abbrev "MA", - :zip "02043"} - {:lat 42.7077684, - :lon -76.4247091, - :house-number "1", - :street "Joseph Drive", - :city "Moravia", - :state-abbrev "NY", - :zip "13118"} - {:lat 42.5332468, - :lon -95.90447019999999, - :house-number "3559", - :street "120th Street", - :city "Pierson", - :state-abbrev "IA", - :zip "51048"} - {:lat 32.6073554, - :lon -86.11205770000001, - :house-number "6695-7143", - :street "Georgia Road", - :city "Wetumpka", - :state-abbrev "AL", - :zip "36092"} - {:lat 44.7075747, - :lon -100.1775982, - :house-number "29831-29999", - :street "185th Street", - :city "Onida", - :state-abbrev "SD", - :zip "57564"} - {:lat 31.458724, - :lon -82.1390318, - :house-number "6046-7330", - :street "Scenic Drive", - :city "Patterson", - :state-abbrev "GA", - :zip "31557"} - {:lat 39.365628, - :lon -84.85064, - :house-number "5007", - :street "Wesley Chapel Road", - :city "West Harrison", - :state-abbrev "IN", - :zip "47060"} - {:lat 30.7871609, - :lon -87.04861149999999, - :house-number "6478", - :street "Southridge Road", - :city "Milton", - :state-abbrev "FL", - :zip "32570"} - {:lat 47.1320838, - :lon -118.525546, - :house-number "901-939", - :street "Rosenoff Road", - :city "Ritzville", - :state-abbrev "WA", - :zip "99169"} - {:lat 47.8067285, - :lon -110.0965646, - :house-number "11533", - :street "Flat Creek Road", - :city "Geraldine", - :state-abbrev "MT", - :zip "59446"} - {:lat 42.780005, - :lon -78.729653, - :house-number "3837", - :street "Freeman Road", - :city "Orchard Park", - :state-abbrev "NY", - :zip "14127"} - {:lat 30.1642049, - :lon -100.0199915, - :house-number "397", - :street "Sd 32740", - :city "Rocksprings", - :state-abbrev "TX", - :zip "78880"} - {:lat 34.306449, - :lon -89.653038, - :house-number "484", - :street "County Road 343", - :city "Taylor", - :state-abbrev "MS", - :zip "38673"} - {:lat 31.7038236, - :lon -96.1186999, - :house-number "145", - :street "Pr 507", - :city "Fairfield", - :state-abbrev "TX", - :zip "75840"} - {:lat 44.9953209, - :lon -93.641418, - :house-number "4680", - :street "Creekwood Trail", - :city "Maple Plain", - :state-abbrev "MN", - :zip "55359"} - {:lat 41.7620177, - :lon -99.2775448, - :house-number "45908", - :street "825th Road", - :city "Burwell", - :state-abbrev "NE", - :zip "68823"} - {:lat 40.401603, - :lon -98.7812086, - :house-number "401-499", - :street "41 Road", - :city "Minden", - :state-abbrev "NE", - :zip "68959"} - {:lat 48.9263308, - :lon -99.0443299, - :house-number "10414-10498", - :street "76th Avenue Northeast", - :city "Sarles", - :state-abbrev "ND", - :zip "58372"} - {:lat 39.9552359, - :lon -76.9656205, - :house-number "53", - :street "Eisenhart Mill Road", - :city "East Berlin", - :state-abbrev "PA", - :zip "17316"} - {:lat 38.177664, - :lon -107.789232, - :house-number "3073", - :street "County Road 24", - :city "Ridgway", - :state-abbrev "CO", - :zip "81432"} - {:lat 47.1157639, - :lon -98.19402960000001, - :house-number "2050-2100", - :street "109th Avenue Southeast", - :city "Dazey", - :state-abbrev "ND", - :zip "58429"} - {:lat 34.8068352, - :lon -96.3323535, - :house-number "5201", - :street "North 376 Road", - :city "Allen", - :state-abbrev "OK", - :zip "74825"} - {:lat 39.2305368, - :lon -104.3967876, - :house-number "14041-14645", - :street "County Road 102", - :city "Elbert", - :state-abbrev "CO", - :zip "80106"} - {:lat 42.0337619, - :lon -71.6630163, - :house-number "131", - :street "Laurel Street", - :city "Uxbridge", - :state-abbrev "MA", - :zip "01569"} - {:lat 36.1576989, - :lon -76.9172385, - :house-number "915", - :street "Elm Grove Road", - :city "Colerain", - :state-abbrev "NC", - :zip "27924"} - {:lat 46.5779436, - :lon -111.2284119, - :house-number "835", - :street "Camas Creek Road", - :city "White Sulphur Springs", - :state-abbrev "MT", - :zip "59645"} - {:lat 48.39654789999999, - :lon -114.4081103, - :house-number "2875", - :street "U.S. 93", - :city "Whitefish", - :state-abbrev "MT", - :zip "59937"} - {:lat 38.1592284, - :lon -95.5663465, - :house-number "935", - :street "Verdure Road Southeast", - :city "Le Roy", - :state-abbrev "KS", - :zip "66857"} - {:lat 48.4095875, - :lon -95.5801213, - :house-number "46801", - :street "White Wolf Road Northwest", - :city "Grygla", - :state-abbrev "MN", - :zip "56727"} - {:lat 40.9809265, - :lon -86.92298989999999, - :house-number "500", - :street "South 5 Mi W Of 1600w", - :city "Francesville", - :state-abbrev "IN", - :zip "47946"} - {:lat 46.9383783, - :lon -99.5487438, - :house-number "4281-4371", - :street "33rd Street Southeast", - :city "Tappen", - :state-abbrev "ND", - :zip "58487"} - {:lat 30.2242984, - :lon -93.587592, - :house-number "257", - :street "Bigwoods Vinton Road", - :city "Vinton", - :state-abbrev "LA", - :zip "70668"} - {:lat 44.4086145, - :lon -68.4235918, - :house-number "1966", - :street "Bayside Road", - :city "Trenton", - :state-abbrev "ME", - :zip "04605"} - {:lat 41.008783, - :lon -105.5615779, - :house-number "244", - :street "Elk Crossing Road", - :city "Tie Siding", - :state-abbrev "WY", - :zip "82084"} - {:lat 35.7386538, - :lon -81.82084689999999, - :house-number "2510-2684", - :street "Conley Bumgarner Road", - :city "Morganton", - :state-abbrev "NC", - :zip "28655"} - {:lat 32.5134911, - :lon -104.9464219, - :house-number "5-101", - :street "G-032", - :city "Dell City", - :state-abbrev "NM", - :zip "79837"} - {:lat 38.427788, - :lon -106.534436, - :house-number "64103", - :street "U.S. 50", - :city "Gunnison", - :state-abbrev "CO", - :zip "81230"} - {:lat 31.5035347, - :lon -111.284977, - :house-number "2805", - :street "Ruby Road", - :city "Nogales", - :state-abbrev "AZ", - :zip "85621"} - {:lat 43.986271, - :lon -94.18518399999999, - :house-number "52136", - :street "150th Street", - :city "Vernon Center", - :state-abbrev "MN", - :zip "56090"} - {:lat 45.30644900000001, - :lon -91.877477, - :house-number "1363", - :street "7th Avenue", - :city "Hillsdale", - :state-abbrev "WI", - :zip "54733"} - {:lat 43.024329, - :lon -112.824998, - :house-number "2678", - :street "West 1200 South", - :city "Aberdeen", - :state-abbrev "ID", - :zip "83210"} - {:lat 39.8614895, - :lon -108.2667581, - :house-number "21144-22214", - :street "County Road 5", - :city "Rifle", - :state-abbrev "CO", - :zip "81650"} - {:lat 33.6274642, - :lon -105.8989291, - :house-number "12167", - :street "U.S. 54", - :city "Carrizozo", - :state-abbrev "NM", - :zip "88301"} - {:lat 39.9160374, - :lon -75.3583661, - :house-number "201-299", - :street "Papermill Road", - :city "Springfield", - :state-abbrev "PA", - :zip "19064"} - {:lat 34.8792474, - :lon -103.0762503, - :house-number "300-498", - :street "Curry Road 43", - :city "Broadview", - :state-abbrev "NM", - :zip "88112"} - {:lat 39.2079099, - :lon -83.12939790000001, - :house-number "1477-1645", - :street "Ohio 772", - :city "Bainbridge", - :state-abbrev "OH", - :zip "45612"} - {:lat 48.4928326, - :lon -100.6789272, - :house-number "7425", - :street "1st Avenue North", - :city "Towner", - :state-abbrev "ND", - :zip "58788"} - {:lat 47.316261, - :lon -111.2672231, - :house-number "1210", - :street "Eden Road", - :city "Great Falls", - :state-abbrev "MT", - :zip "59405"} - {:lat 40.370281, - :lon -76.759902, - :house-number "1016", - :street "Piketown Road", - :city "Harrisburg", - :state-abbrev "PA", - :zip "17112"} - {:lat 38.358731, - :lon -97.919955, - :house-number "27", - :street "Kiowa Road", - :city "Windom", - :state-abbrev "KS", - :zip "67491"} - {:lat 32.0771818, - :lon -84.0684537, - :house-number "1699", - :street "Georgia 195", - :city "Americus", - :state-abbrev "GA", - :zip "31709"} - {:lat 40.1744085, - :lon -82.5928914, - :house-number "4500-6098", - :street "Dutch Lane Northwest", - :city "Johnstown", - :state-abbrev "OH", - :zip "43031"} - {:lat 39.8006462, - :lon -106.2671922, - :house-number "1465", - :street "Black Creek Road", - :city "Silverthorne", - :state-abbrev "CO", - :zip "80498"} - {:lat 39.031739, - :lon -88.42046599999999, - :house-number "8047", - :street "North 2100th Street", - :city "Dieterich", - :state-abbrev "IL", - :zip "62424"} - {:lat 40.8826954, - :lon -84.1866254, - :house-number "21900-21998", - :street "Road 17", - :city "Columbus Grove", - :state-abbrev "OH", - :zip "45830"} - {:lat 35.8027731, - :lon -80.48833259999999, - :house-number "224-238", - :street "Singleton Road", - :city "Mocksville", - :state-abbrev "NC", - :zip "27028"} - {:lat 39.2667106, - :lon -83.03588740000001, - :house-number "484", - :street "Baptist Hill Road", - :city "Chillicothe", - :state-abbrev "OH", - :zip "45601"} - {:lat 47.9624757, - :lon -106.0816186, - :house-number "339", - :street "Maxwell Hill Road", - :city "Wolf Point", - :state-abbrev "MT", - :zip "59201"} - {:lat 36.609706, - :lon -85.05463, - :house-number "3072", - :street "Chanute Road", - :city "Pall Mall", - :state-abbrev "TN", - :zip "38577"} - {:lat 37.286474, - :lon -121.862554, - :house-number "741", - :street "Batista Drive", - :city "San Jose", - :state-abbrev "CA", - :zip "95136"} - {:lat 36.6981921, - :lon -92.1128186, - :house-number "9700", - :street "County Road 7950", - :city "Pottersville", - :state-abbrev "MO", - :zip "65790"} - {:lat 32.4484452, - :lon -81.8748105, - :house-number "801", - :street "Brannen Cemetery Road", - :city "Statesboro", - :state-abbrev "GA", - :zip "30458"} - {:lat 29.5870185, - :lon -95.8321783, - :house-number "693-799", - :street "Huntington Road", - :city "Rosenberg", - :state-abbrev "TX", - :zip "77471"} - {:lat 32.7711839, - :lon -91.7136507, - :house-number "11638", - :street "Johnson School Road", - :city "Mer Rouge", - :state-abbrev "LA", - :zip "71261"} - {:lat 43.9268497, - :lon -101.7960548, - :house-number "23950", - :street "Recluse Road", - :city "Philip", - :state-abbrev "SD", - :zip "57567"} - {:lat 39.3069469, - :lon -99.3298011, - :house-number "1528-1598", - :street "South Road", - :city "Plainville", - :state-abbrev "KS", - :zip "67663"} - {:lat 35.0778421, - :lon -85.1800033, - :house-number "7700-7706", - :street "Cecelia Drive", - :city "Chattanooga", - :state-abbrev "TN", - :zip "37416"} - {:lat 41.3652452, - :lon -75.2521935, - :house-number "106", - :street "Spruce Lane", - :city "Greentown", - :state-abbrev "PA", - :zip "18426"} - {:lat 33.7798117, - :lon -117.4158073, - :house-number "20090", - :street "Chalon Road", - :city "Perris", - :state-abbrev "CA", - :zip "92570"} - {:lat 43.1617463, - :lon -92.7789888, - :house-number "1350", - :street "River Road", - :city "Floyd", - :state-abbrev "IA", - :zip "50435"} - {:lat 35.1246269, - :lon -87.35063, - :house-number "20", - :street "Hardiman Road South", - :city "Leoma", - :state-abbrev "TN", - :zip "38468"} - {:lat 42.38576, - :lon -96.4448319, - :house-number "1943-1957", - :street "U.S. 75", - :city "Dakota City", - :state-abbrev "NE", - :zip "68731"} - {:lat 41.578093, - :lon -80.631832, - :house-number "6339", - :street "Creek Road", - :city "Andover", - :state-abbrev "OH", - :zip "44003"} - {:lat 41.383577, - :lon -93.369366, - :house-number "9537", - :street "228th Avenue", - :city "Ackworth", - :state-abbrev "IA", - :zip "50001"} - {:lat 37.8261403, - :lon -83.955142, - :house-number "2188-2226", - :street "Spout Springs Road", - :city "Clay City", - :state-abbrev "KY", - :zip "40312"} - {:lat 44.755191, - :lon -106.673704, - :house-number "924", - :street "Ulm Road", - :city "Banner", - :state-abbrev "WY", - :zip "82832"} - {:lat 45.1755568, - :lon -89.4512337, - :house-number "W593", - :street "Spring Brook Avenue", - :city "Merrill", - :state-abbrev "WI", - :zip "54452"} - {:lat 29.633604, - :lon -100.9240923, - :house-number "354", - :street "Summit Drive", - :city "Del Rio", - :state-abbrev "TX", - :zip "78840"} - {:lat 46.640867, - :lon -67.968823, - :house-number "157", - :street "Centerline Road", - :city "Presque Isle", - :state-abbrev "ME", - :zip "04769"} - {:lat 46.9913099, - :lon -104.2749999, - :house-number "372", - :street "Hodges Road", - :city "Wibaux", - :state-abbrev "MT", - :zip "59353"} - {:lat 40.110004, - :lon -75.957448, - :house-number "481", - :street "South Churchtown Road", - :city "Narvon", - :state-abbrev "PA", - :zip "17555"} - {:lat 34.2383253, - :lon -82.8546978, - :house-number "1567", - :street "Coldwater Road", - :city "Dewy Rose", - :state-abbrev "GA", - :zip "30634"} - {:lat 33.92903100000001, - :lon -83.678337, - :house-number "1405", - :street "Gin Mill Court", - :city "Monroe", - :state-abbrev "GA", - :zip "30656"} - {:lat 44.45397810000001, - :lon -75.5898072, - :house-number "701", - :street "Turner Road", - :city "Hammond", - :state-abbrev "NY", - :zip "13646"} - {:lat 45.8027586, - :lon -92.7680673, - :house-number "37656", - :street "Black Pine Road", - :city "Pine City", - :state-abbrev "MN", - :zip "55063"} - {:lat 41.0614976, - :lon -110.5364236, - :house-number "271", - :street "County Road", - :city "Fort Bridger", - :state-abbrev "WY", - :zip "82933"} - {:lat 33.096877, - :lon -116.1325306, - :house-number "6599", - :street "Alvarado Street", - :city "Borrego Springs", - :state-abbrev "CA", - :zip "92004"} - {:lat 46.3109698, - :lon -84.8021358, - :house-number "13785-13865", - :street "Sullivan Creek Trail", - :city "Rudyard", - :state-abbrev "MI", - :zip "49780"} - {:lat 39.8092853, - :lon -121.7420029, - :house-number "342", - :street "4 Acres Sec34 T23nr2e", - :city "Chico", - :state-abbrev "CA", - :zip "95928"} - {:lat 39.0242849, - :lon -82.2628741, - :house-number "30945", - :street "Ohio 325", - :city "Langsville", - :state-abbrev "OH", - :zip "45741"} - {:lat 40.900784, - :lon -86.99796900000001, - :house-number "7577", - :street "South County Road 210 East", - :city "Rensselaer", - :state-abbrev "IN", - :zip "47978"} - {:lat 40.8104888, - :lon -90.2666866, - :house-number "641", - :street "Knox Road 900 East", - :city "Gilson", - :state-abbrev "IL", - :zip "61436"} - {:lat 46.6261015, - :lon -108.6118494, - :house-number "125-177", - :street "Snowy Mountain Road", - :city "Roundup", - :state-abbrev "MT", - :zip "59072"} - {:lat 46.855473, - :lon -117.7997662, - :house-number "272", - :street "Guske Road", - :city "LaCrosse", - :state-abbrev "WA", - :zip "99143"} - {:lat 30.3724246, - :lon -85.43729569999999, - :house-number "12404", - :street "U.S. 231", - :city "Youngstown", - :state-abbrev "FL", - :zip "32466"} - {:lat 42.595562, - :lon -121.743452, - :house-number "41351", - :street "Solomon Drive", - :city "Chiloquin", - :state-abbrev "OR", - :zip "97624"} - {:lat 45.6720056, - :lon -88.93579230000002, - :house-number "9942", - :street "Suring Lane", - :city "Argonne", - :state-abbrev "WI", - :zip "54511"} - {:lat 31.3300691, - :lon -85.97398249999999, - :house-number "23-999", - :street "County Road 545", - :city "New Brockton", - :state-abbrev "AL", - :zip "36351"} - {:lat 37.7946338, - :lon -83.4015648, - :house-number "141", - :street "Blankenship Road", - :city "Hazel Green", - :state-abbrev "KY", - :zip "41332"} - {:lat 40.28411759999999, - :lon -74.2302674, - :house-number "200", - :street "Sunnyside Drive", - :city "Marlboro Township", - :state-abbrev "NJ", - :zip "07746"} - {:lat 40.9640534, - :lon -91.02591079999999, - :house-number "4746-4998", - :street "195th Street", - :city "Burlington", - :state-abbrev "IA", - :zip "52601"} - {:lat 39.516798, - :lon -82.225859, - :house-number "44600", - :street "Dawley-New Pittsburg Road", - :city "Nelsonville", - :state-abbrev "OH", - :zip "45764"} - {:lat 39.2393343, - :lon -79.0159398, - :house-number "71", - :street "Meadow Farms Road", - :city "Old Fields", - :state-abbrev "WV", - :zip "26845"} - {:lat 39.72992869999999, - :lon -106.1713332, - :house-number "2642", - :street "Meadowbrook Lane", - :city "Silverthorne", - :state-abbrev "CO", - :zip "80498"} - {:lat 41.9823355, - :lon -90.4063305, - :house-number "3536-3620", - :street "135th Street", - :city "Charlotte", - :state-abbrev "IA", - :zip "52731"} - {:lat 46.9822916, - :lon -107.2805484, - :house-number "487", - :street "South Sand Creek Road", - :city "Jordan", - :state-abbrev "MT", - :zip "59337"} - {:lat 41.4528115, - :lon -74.9944647, - :house-number "157-171", - :street "Neil Thompson Road", - :city "Shohola", - :state-abbrev "PA", - :zip "18458"} - {:lat 44.733007, - :lon -73.408199, - :house-number "275", - :street "Allen Road", - :city "Plattsburgh", - :state-abbrev "NY", - :zip "12901"} - {:lat 42.765204, - :lon -89.598237, - :house-number "N7109", - :street "Wettach Road", - :city "Monticello", - :state-abbrev "WI", - :zip "53570"} - {:lat 40.997771, - :lon -85.092398, - :house-number "9431", - :street "Hessen Cassel Road", - :city "Fort Wayne", - :state-abbrev "IN", - :zip "46816"} - {:lat 36.5902185, - :lon -98.8046696, - :house-number "35860", - :street "Oklahoma 45", - :city "Waynoka", - :state-abbrev "OK", - :zip "73860"} - {:lat 39.342818, - :lon -89.420245, - :house-number "23000-23220", - :street "East 15th Road", - :city "Morrisonville", - :state-abbrev "IL", - :zip "62546"} - {:lat 35.9029025, - :lon -77.8467766, - :house-number "11905", - :street "North Carolina 97", - :city "Rocky Mount", - :state-abbrev "NC", - :zip "27803"} - {:lat 45.9727092, - :lon -91.1340783, - :house-number "9032-9056", - :street "West County Highway B", - :city "Hayward", - :state-abbrev "WI", - :zip "54843"} - {:lat 41.4097631, - :lon -112.431176, - :house-number "5311", - :street "Southeast Promontory Road", - :city "Corinne", - :state-abbrev "UT", - :zip "84307"} - {:lat 34.9942849, - :lon -89.7761688, - :house-number "12577", - :street "State Line Road", - :city "Olive Branch", - :state-abbrev "MS", - :zip "38654"} - {:lat 34.4754932, - :lon -109.5960396, - :house-number "37232", - :street "Arizona 61", - :city "Concho", - :state-abbrev "AZ", - :zip "85924"} - {:lat 46.03810379999999, - :lon -91.0169315, - :house-number "11715", - :street "North Fr 174", - :city "Hayward", - :state-abbrev "WI", - :zip "54843"} - {:lat 38.0881508, - :lon -94.7917923, - :house-number "4708", - :street "Paine Road", - :city "Mound City", - :state-abbrev "KS", - :zip "66056"} - {:lat 36.489802, - :lon -88.5697129, - :house-number "740", - :street "Harrison Road", - :city "Palmersville", - :state-abbrev "TN", - :zip "38241"} - {:lat 38.9832517, - :lon -122.1164638, - :house-number "6225", - :street "Boles Road", - :city "Arbuckle", - :state-abbrev "CA", - :zip "95912"} - {:lat 42.1780297, - :lon -72.9587426, - :house-number "168", - :street "Otis Stage Road", - :city "Blandford", - :state-abbrev "MA", - :zip "01008"} - {:lat 40.1638939, - :lon -78.13666529999999, - :house-number "97", - :street "Po Box", - :city "Wood", - :state-abbrev "PA", - :zip "16694"} - {:lat 38.0254997, - :lon -85.1213398, - :house-number "3800", - :street "Murphy Lane", - :city "Mount Eden", - :state-abbrev "KY", - :zip "40046"} - {:lat 34.0996574, - :lon -102.5112853, - :house-number "1563", - :street "FM 303", - :city "Sudan", - :state-abbrev "TX", - :zip "79371"} - {:lat 44.15514599999999, - :lon -87.875097, - :house-number "14428", - :street "San Road", - :city "Cato", - :state-abbrev "WI", - :zip "54230"} - {:lat 46.7882987, - :lon -119.1126247, - :house-number "800-942", - :street "South Steele Road", - :city "Othello", - :state-abbrev "WA", - :zip "99344"} - {:lat 35.5156836, - :lon -77.82581379999999, - :house-number "384", - :street "Landis Road", - :city "Stantonsburg", - :state-abbrev "NC", - :zip "27883"} - {:lat 35.3017766, - :lon -118.8785867, - :house-number "5064-6362", - :street "South Edison Road", - :city "Bakersfield", - :state-abbrev "CA", - :zip "93307"} - {:lat 35.6617665, - :lon -81.5617742, - :house-number "1916", - :street "Off Road", - :city "Morganton", - :state-abbrev "NC", - :zip "28655"} - {:lat 40.264102, - :lon -85.24811199999999, - :house-number "11910", - :street "East Co Road 500 North", - :city "Albany", - :state-abbrev "IN", - :zip "47320"} - {:lat 34.3691713, - :lon -85.9096969, - :house-number "509", - :street "County Road 49", - :city "Dawson", - :state-abbrev "AL", - :zip "35963"} - {:lat 40.639914, - :lon -91.537768, - :house-number "1939", - :street "Iowa 2", - :city "Donnellson", - :state-abbrev "IA", - :zip "52625"} - {:lat 48.367617, - :lon -106.386911, - :house-number "1172", - :street "Geer Road", - :city "Nashua", - :state-abbrev "MT", - :zip "59248"} - {:lat 37.5534413, - :lon -80.6684452, - :house-number "153", - :street "Laurel Creek Road", - :city "Greenville", - :state-abbrev "WV", - :zip "24945"} - {:lat 48.5129061, - :lon -107.450546, - :house-number "4972", - :street "Lake Road", - :city "Saco", - :state-abbrev "MT", - :zip "59261"} - {:lat 44.303249, - :lon -122.693011, - :house-number "42750", - :street "Upper Calapooia Drive", - :city "Sweet Home", - :state-abbrev "OR", - :zip "97386"} - {:lat 37.968924, - :lon -92.4927889, - :house-number "1185", - :street "Mountview Road", - :city "Richland", - :state-abbrev "MO", - :zip "65556"} - {:lat 40.3180455, - :lon -99.34456770000001, - :house-number "72382", - :street "O Road", - :city "Holdrege", - :state-abbrev "NE", - :zip "68949"} - {:lat 37.7455954, - :lon -75.7387931, - :house-number "22317", - :street "Deep Creek Road", - :city "Onancock", - :state-abbrev "VA", - :zip "23417"} - {:lat 35.266777, - :lon -87.098073, - :house-number "305", - :street "Ball Hollow Estate Road", - :city "Pulaski", - :state-abbrev "TN", - :zip "38478"} - {:lat 60.04188310000001, - :lon -151.3121206, - :house-number "54117", - :street "Sisler Avenue", - :city "Ninilchik", - :state-abbrev "AK", - :zip "99639"} - {:lat 45.1750936, - :lon -88.81815429999999, - :house-number "4098", - :street "Fire Lane Road", - :city "White Lake", - :state-abbrev "WI", - :zip "54491"} - {:lat 32.3732398, - :lon -98.9501281, - :house-number "15151", - :street "Interstate 20", - :city "Cisco", - :state-abbrev "TX", - :zip "76437"} - {:lat 42.8860049, - :lon -84.9869425, - :house-number "4381", - :street "Klotz Road", - :city "Portland", - :state-abbrev "MI", - :zip "48875"} - {:lat 34.9344866, - :lon -89.4021398, - :house-number "2355-2665", - :street "Roberts Chapel Road", - :city "Lamar", - :state-abbrev "MS", - :zip "38642"} - {:lat 33.67856039999999, - :lon -97.8830539, - :house-number "159", - :street "East Rc Road", - :city "Bowie", - :state-abbrev "TX", - :zip "76230"} - {:lat 42.5489375, - :lon -85.1945466, - :house-number "4770", - :street "Maple Grove Road", - :city "Hastings", - :state-abbrev "MI", - :zip "49058"} - {:lat 41.52784, - :lon -91.725444, - :house-number "2096", - :street "560th Street Southwest", - :city "Kalona", - :state-abbrev "IA", - :zip "52247"} - {:lat 35.6193554, - :lon -85.92264879999999, - :house-number "724-1350", - :street "Jacksboro Road", - :city "Morrison", - :state-abbrev "TN", - :zip "37357"} - {:lat 36.399259, - :lon -76.2899489, - :house-number "102", - :street "Robin Drive", - :city "South Mills", - :state-abbrev "NC", - :zip "27976"} - {:lat 36.5620566, - :lon -80.0153004, - :house-number "175", - :street "Democrat Road", - :city "Spencer", - :state-abbrev "VA", - :zip "24165"} - {:lat 48.5784473, - :lon -101.3426061, - :house-number "8000-8054", - :street "29th Avenue Northwest", - :city "Lansford", - :state-abbrev "ND", - :zip "58750"} - {:lat 46.1444883, - :lon -91.37233309999999, - :house-number "14108", - :street "North Sonby Road", - :city "Hayward", - :state-abbrev "WI", - :zip "54843"} - {:lat 42.7471583, - :lon -82.90010029999999, - :house-number "21875-22399", - :street "28 Mile Road", - :city "Ray", - :state-abbrev "MI", - :zip "48096"} - {:lat 41.11358389999999, - :lon -86.38228640000001, - :house-number "7346", - :street "West 400 North", - :city "Rochester", - :state-abbrev "IN", - :zip "46975"} - {:lat 38.5410306, - :lon -105.030577, - :house-number "7502", - :street "Upper Beaver Creek Road", - :city "Penrose", - :state-abbrev "CO", - :zip "81240"} - {:lat 36.0810719, - :lon -81.57605339999999, - :house-number "2300-2332", - :street "Timber Run Drive", - :city "Lenoir", - :state-abbrev "NC", - :zip "28645"} - {:lat 48.5178342, - :lon -103.8474234, - :house-number "14601-14649", - :street "76th Street Northwest", - :city "Grenora", - :state-abbrev "ND", - :zip "58845"} - {:lat 35.3585085, - :lon -100.3074713, - :house-number "7329-7499", - :street "County Road 13", - :city "Wheeler", - :state-abbrev "TX", - :zip "79096"} - {:lat 46.1712919, - :lon -118.8883021, - :house-number "3500-3568", - :street "East Humorist Road", - :city "Burbank", - :state-abbrev "WA", - :zip "99323"} - {:lat 37.9988509, - :lon -92.65144699999999, - :house-number "1443", - :street "Lowell Williams Road", - :city "Linn Creek", - :state-abbrev "MO", - :zip "65052"} - {:lat 44.7876962, - :lon -94.28539289999999, - :house-number "11701", - :street "Melody Avenue", - :city "Glencoe", - :state-abbrev "MN", - :zip "55336"} - {:lat 28.375405, - :lon -81.649492, - :house-number "17600", - :street "Flemmings Road", - :city "Winter Garden", - :state-abbrev "FL", - :zip "34787"} - {:lat 42.6134894, - :lon -82.9754203, - :house-number "43314", - :street "Hillcrest Drive", - :city "Sterling Heights", - :state-abbrev "MI", - :zip "48313"} - {:lat 35.229939, - :lon -88.711297, - :house-number "1263", - :street "Curtis Hill Church Lane", - :city "Bethel Springs", - :state-abbrev "TN", - :zip "38315"} - {:lat 28.2967559, - :lon -81.84640279999999, - :house-number "A3", - :street "Van Fleet Road", - :city "Polk City", - :state-abbrev "FL", - :zip "33868"} - {:lat 48.94182, - :lon -94.92195559999999, - :house-number "6298", - :street "Northwest Sandy Shores Drive", - :city "Williams", - :state-abbrev "MN", - :zip "56686"} - {:lat 32.146161, - :lon -86.406871, - :house-number "1224", - :street "Plantation Road", - :city "Hope Hull", - :state-abbrev "AL", - :zip "36043"} - {:lat 27.472142, - :lon -80.431026, - :house-number "2001", - :street "Old Ffa Road", - :city "Fort Pierce", - :state-abbrev "FL", - :zip "34951"} - {:lat 32.270188, - :lon -90.9108059, - :house-number "400", - :street "Wilbert Lane", - :city "Vicksburg", - :state-abbrev "MS", - :zip "39180"} - {:lat 34.8768839, - :lon -85.24801699999999, - :house-number "1174", - :street "Red Belt Road", - :city "Chickamauga", - :state-abbrev "GA", - :zip "30707"} - {:lat 35.5362926, - :lon -98.2195119, - :house-number "23861", - :street "Route 66", - :city "Calumet", - :state-abbrev "OK", - :zip "73014"} - {:lat 38.6162698, - :lon -89.3774191, - :house-number "1511-1591", - :street "Kane Street", - :city "Carlyle", - :state-abbrev "IL", - :zip "62231"} - {:lat 43.4144182, - :lon -98.64741860000001, - :house-number "27358-27398", - :street "377th Avenue", - :city "Corsica", - :state-abbrev "SD", - :zip "57328"} - {:lat 34.271229, - :lon -87.1796677, - :house-number "99", - :street "County Road 3706", - :city "Addison", - :state-abbrev "AL", - :zip "35540"} - {:lat 32.862158, - :lon -108.5860274, - :house-number "473", - :street "Bill Evans Road", - :city "Silver City", - :state-abbrev "NM", - :zip "88061"} - {:lat 37.18846610000001, - :lon -77.9081442, - :house-number "3543", - :street "Rocky Hill Road", - :city "Blackstone", - :state-abbrev "VA", - :zip "23824"} - {:lat 47.959813, - :lon -106.0802946, - :house-number "339", - :street "Maxwell Hill Road", - :city "Wolf Point", - :state-abbrev "MT", - :zip "59201"} - {:lat 32.09018080000001, - :lon -84.41532629999999, - :house-number "1460", - :street "Youngs Mill Road", - :city "Plains", - :state-abbrev "GA", - :zip "31780"} - {:lat 35.2428253, - :lon -78.0706555, - :house-number "756", - :street "Country Club Road", - :city "Mount Olive", - :state-abbrev "NC", - :zip "28365"} - {:lat 46.4567953, - :lon -98.09833069999999, - :house-number "11079-11099", - :street "66th Street Southeast", - :city "Verona", - :state-abbrev "ND", - :zip "58490"} - {:lat 36.8523157, - :lon -87.5900484, - :house-number "3095", - :street "Hensley Lane", - :city "Hopkinsville", - :state-abbrev "KY", - :zip "42240"} - {:lat 29.836327, - :lon -97.43976699999999, - :house-number "1026", - :street "Pine Gap Drive", - :city "Dale", - :state-abbrev "TX", - :zip "78616"} - {:lat 34.0710594, - :lon -79.6735174, - :house-number "4139", - :street "Planer Road", - :city "Effingham", - :state-abbrev "SC", - :zip "29541"} - {:lat 31.477583, - :lon -98.133838, - :house-number "1636", - :street "U.S. 84", - :city "Evant", - :state-abbrev "TX", - :zip "76525"} - {:lat 41.5298448, - :lon -92.1890238, - :house-number "1564", - :street "325th Street", - :city "North English", - :state-abbrev "IA", - :zip "52316"} - {:lat 35.6221391, - :lon -79.2516907, - :house-number "1118", - :street "Cole Thomas Road", - :city "Bear Creek", - :state-abbrev "NC", - :zip "27207"} - {:lat 38.760812, - :lon -77.74101399999999, - :house-number "6742", - :street "Kirk Lane", - :city "Warrenton", - :state-abbrev "VA", - :zip "20187"} - {:lat 37.7204679, - :lon -119.934325, - :house-number "8622", - :street "Bull Creek Road", - :city "Coulterville", - :state-abbrev "CA", - :zip "95311"} - {:lat 31.4295568, - :lon -85.08885599999999, - :house-number "2-8", - :street "Henry County 47", - :city "Shorterville", - :state-abbrev "AL", - :zip "36373"} - {:lat 44.25241339999999, - :lon -86.02472209999999, - :house-number "13841", - :street "Chicago Avenue", - :city "Wellston", - :state-abbrev "MI", - :zip "49689"} - {:lat 40.9866609, - :lon -124.0667605, - :house-number "240", - :street "Wagle Lane", - :city "McKinleyville", - :state-abbrev "CA", - :zip "95519"} - {:lat 44.94850520000001, - :lon -117.9031187, - :house-number "46973", - :street "Haines Dump Road", - :city "Haines", - :state-abbrev "OR", - :zip "97833"} - {:lat 44.4356448, - :lon -121.9428254, - :house-number "13000", - :street "U.S. 20", - :city "Sisters", - :state-abbrev "OR", - :zip "97759"} - {:lat 30.2388971, - :lon -91.3172204, - :house-number "27729-27757", - :street "Intracoastal Road", - :city "Plaquemine", - :state-abbrev "LA", - :zip "70764"} - {:lat 45.1555286, - :lon -91.9936049, - :house-number "3254-3294", - :street "1300th Avenue", - :city "Ridgeland", - :state-abbrev "WI", - :zip "54763"} - {:lat 32.308539, - :lon -110.793313, - :house-number "5419", - :street "Shandon Place", - :city "Tucson", - :state-abbrev "AZ", - :zip "85749"} - {:lat 45.6170256, - :lon -90.4052562, - :house-number "N6687", - :street "Aspen Road", - :city "Phillips", - :state-abbrev "WI", - :zip "54555"} - {:lat 38.5529525, - :lon -86.2359341, - :house-number "8335", - :street "West Vincennes Trail", - :city "Campbellsburg", - :state-abbrev "IN", - :zip "47108"} - {:lat 44.407822, - :lon -92.6516345, - :house-number "37000-37912", - :street "190th Avenue", - :city "Goodhue", - :state-abbrev "MN", - :zip "55027"} - {:lat 41.2424746, - :lon -75.7229472, - :house-number "3001", - :street "Bald Mountain Road", - :city "Bear Creek Village", - :state-abbrev "PA", - :zip "18702"} - {:lat 40.8950958, - :lon -100.366425, - :house-number "36844", - :street "East Palmer Road", - :city "Brady", - :state-abbrev "NE", - :zip "69123"} - {:lat 33.4835755, - :lon -97.5134693, - :house-number "2840", - :street "Seldom Seen Road", - :city "Forestburg", - :state-abbrev "TX", - :zip "76239"} - {:lat 45.304834, - :lon -91.5486899, - :house-number "2963", - :street "7th Avenue", - :city "Chetek", - :state-abbrev "WI", - :zip "54728"} - {:lat 43.782105, - :lon -76.125839, - :house-number "7859", - :street "Lake Road", - :city "Belleville", - :state-abbrev "NY", - :zip "13611"} - {:lat 44.32127819999999, - :lon -97.9138131, - :house-number "21101-21199", - :street "415th Avenue", - :city "Iroquois", - :state-abbrev "SD", - :zip "57353"} - {:lat 42.7269199, - :lon -83.8923518, - :house-number "6876", - :street "Dean Road", - :city "Howell", - :state-abbrev "MI", - :zip "48855"} - {:lat 39.4287889, - :lon -84.1343097, - :house-number "3824-3938", - :street "Wilmington Road", - :city "Lebanon", - :state-abbrev "OH", - :zip "45036"} - {:lat 34.361765, - :lon -93.3366379, - :house-number "122", - :street "Falls Lane", - :city "Bonnerdale", - :state-abbrev "AR", - :zip "71933"} - {:lat 29.770311, - :lon -98.291823, - :house-number "628-998", - :street "Seay Lane", - :city "New Braunfels", - :state-abbrev "TX", - :zip "78132"} - {:lat 40.133207, - :lon -108.191913, - :house-number "3200", - :street "Penny Drive", - :city "Meeker", - :state-abbrev "CO", - :zip "81641"} - {:lat 40.77145489999999, - :lon -111.8558905, - :house-number "1100-1116", - :street "East 2nd Avenue", - :city "Salt Lake City", - :state-abbrev "UT", - :zip "84103"} - {:lat 37.84206289999999, - :lon -90.8244859, - :house-number "12949", - :street "Delbridge Road", - :city "Potosi", - :state-abbrev "MO", - :zip "63664"} - {:lat 37.3419049, - :lon -80.828667, - :house-number "510", - :street "Stock Pen Mountain Road", - :city "Narrows", - :state-abbrev "VA", - :zip "24124"} - {:lat 41.0536659, - :lon -91.01103529999999, - :house-number "25504-25986", - :street "42nd Avenue", - :city "Oakville", - :state-abbrev "IA", - :zip "52646"} - {:lat 30.493264, - :lon -98.97051499999999, - :house-number "721", - :street "Wasserfall Road", - :city "Fredericksburg", - :state-abbrev "TX", - :zip "78624"} - {:lat 46.1178152, - :lon -105.8201727, - :house-number "1086", - :street "Tongue River Road", - :city "Miles City", - :state-abbrev "MT", - :zip "59301"} - {:lat 33.156414, - :lon -105.782114, - :house-number "111-199", - :street "Cottonwood Drive", - :city "Mescalero", - :state-abbrev "NM", - :zip "88340"} - {:lat 37.2891389, - :lon -88.05956570000001, - :house-number "1272-1980", - :street "Weldon Road", - :city "Marion", - :state-abbrev "KY", - :zip "42064"} - {:lat 42.30973350000001, - :lon -105.0921531, - :house-number "355", - :street "Coleman Road", - :city "Wheatland", - :state-abbrev "WY", - :zip "82201"} - {:lat 45.008201, - :lon -92.406273, - :house-number "1950", - :street "County Road E", - :city "Baldwin", - :state-abbrev "WI", - :zip "54002"} - {:lat 44.1305376, - :lon -94.9190122, - :house-number "11560", - :street "370th Avenue", - :city "Comfrey", - :state-abbrev "MN", - :zip "56019"} - {:lat 46.7091378, - :lon -101.0908937, - :house-number "4816-4820", - :street "30th Avenue", - :city "Mandan", - :state-abbrev "ND", - :zip "58554"} - {:lat 42.1507858, - :lon -104.9566127, - :house-number "251", - :street "East Johnson Road", - :city "Wheatland", - :state-abbrev "WY", - :zip "82201"} - {:lat 44.9003897, - :lon -102.8874754, - :house-number "17224", - :street "Old 212", - :city "Mud Butte", - :state-abbrev "SD", - :zip "57758"} - {:lat 36.46426599999999, - :lon -89.352084, - :house-number "2460", - :street "Tennessee 213", - :city "Tiptonville", - :state-abbrev "TN", - :zip "38079"} - {:lat 46.7180626, - :lon -113.1962918, - :house-number "195", - :street "Big Sky Ridge Lane", - :city "Drummond", - :state-abbrev "MT", - :zip "59832"} - {:lat 42.927046, - :lon -74.853465, - :house-number "1049", - :street "Travis Road", - :city "Jordanville", - :state-abbrev "NY", - :zip "13361"} - {:lat 35.6341398, - :lon -91.7455191, - :house-number "1000", - :street "Old Union Road", - :city "Floral", - :state-abbrev "AR", - :zip "72534"} - {:lat 37.179507, - :lon -112.386285, - :house-number "7295", - :street "Johnson Canyon Road", - :city "Kanab", - :state-abbrev "UT", - :zip "84741"} - {:lat 43.479399, - :lon -88.583489, - :house-number "N7851", - :street "Bay View Road", - :city "Horicon", - :state-abbrev "WI", - :zip "53032"} - {:lat 30.9974104, - :lon -91.0533414, - :house-number "6310", - :street "Highway 422", - :city "Norwood", - :state-abbrev "LA", - :zip "70761"} - {:lat 38.5767965, - :lon -96.4920547, - :house-number "2099-2199", - :street "South 850 Road", - :city "Council Grove", - :state-abbrev "KS", - :zip "66846"} - {:lat 32.828037, - :lon -115.574871, - :house-number "2420", - :street "Enterprise Way", - :city "Imperial", - :state-abbrev "CA", - :zip "92251"} - {:lat 44.630733, - :lon -73.586221, - :house-number "41", - :street "Austin Road", - :city "Morrisonville", - :state-abbrev "NY", - :zip "12962"} - {:lat 45.0889128, - :lon -123.5819218, - :house-number "24118-24500", - :street "Shadow Lane", - :city "Grand Ronde", - :state-abbrev "OR", - :zip "97347"} - {:lat 39.3111942, - :lon -79.09821339999999, - :house-number "410", - :street "Burgess Hollow Road", - :city "New Creek", - :state-abbrev "WV", - :zip "26743"} - {:lat 30.484739, - :lon -84.59702999999999, - :house-number "7439", - :street "Old Federal Road", - :city "Quincy", - :state-abbrev "FL", - :zip "32351"} - {:lat 47.6658559, - :lon -105.793456, - :house-number "306-318", - :street "Thorgaard Road", - :city "Vida", - :state-abbrev "MT", - :zip "59274"} - {:lat 29.779582, - :lon -96.8117989, - :house-number "12016", - :street "Mazoch Road Holman Area", - :city "La Grange", - :state-abbrev "TX", - :zip "78945"} - {:lat 38.698893, - :lon -85.82955799999999, - :house-number "2999", - :street "County Road 50 North", - :city "Scottsburg", - :state-abbrev "IN", - :zip "47170"} - {:lat 26.630951, - :lon -80.910077, - :house-number "10000", - :street "CR 835", - :city "Clewiston", - :state-abbrev "FL", - :zip "33440"} - {:lat 37.3210317, - :lon -90.1330998, - :house-number "1572", - :street "RR 1", - :city "Glenallen", - :state-abbrev "MO", - :zip "63751"} - {:lat 39.6197165, - :lon -85.6859594, - :house-number "6352-6746", - :street "North Range Line Road", - :city "Shelbyville", - :state-abbrev "IN", - :zip "46176"} - {:lat 46.8348647, - :lon -99.59147390000001, - :house-number "4120", - :street "40th Street Southeast", - :city "Tappen", - :state-abbrev "ND", - :zip "58487"} - {:lat 40.5671369, - :lon -88.81208720000001, - :house-number "21766", - :street "East 1750 North Road", - :city "Towanda", - :state-abbrev "IL", - :zip "61776"} - {:lat 34.8072897, - :lon -87.6318849, - :house-number "800", - :street "South Cox Creek Parkway", - :city "Florence", - :state-abbrev "AL", - :zip "35630"} - {:lat 61.53484349999999, - :lon -166.0989777, - :house-number "1", - :street "Uinaq Road", - :city "Hooper Bay", - :state-abbrev "AK", - :zip "99604"} - {:lat 48.66046, - :lon -112.765961, - :house-number "375", - :street "Meriwether Road", - :city "Cut Bank", - :state-abbrev "MT", - :zip "59427"} - {:lat 48.6799332, - :lon -102.6469106, - :house-number "8701-8767", - :street "91st Avenue Northwest", - :city "Powers Lake", - :state-abbrev "ND", - :zip "58773"} - {:lat 42.5187101, - :lon -74.94718449999999, - :house-number "460", - :street "Burrillo Road", - :city "Maryland", - :state-abbrev "NY", - :zip "12116"} - {:lat 46.1642053, - :lon -96.67889219999999, - :house-number "8601-8649", - :street "178th Avenue Southeast", - :city "Wahpeton", - :state-abbrev "ND", - :zip "58075"} - {:lat 32.0780224, - :lon -85.57917119999999, - :house-number "662", - :street "Foster Road", - :city "Union Springs", - :state-abbrev "AL", - :zip "36089"} - {:lat 36.7672839, - :lon -119.225824, - :house-number "1444", - :street "Crane Lane", - :city "Squaw Valley", - :state-abbrev "CA", - :zip "93675"} - {:lat 31.982205, - :lon -99.095485, - :house-number "5580", - :street "County Road 411", - :city "Brownwood", - :state-abbrev "TX", - :zip "76801"} - {:lat 29.60951, - :lon -98.283165, - :house-number "7015", - :street "Farm to Market Road 3009", - :city "Schertz", - :state-abbrev "TX", - :zip "78154"} - {:lat 44.8499904, - :lon -121.0645029, - :house-number "85104", - :street "South Junction Road", - :city "Maupin", - :state-abbrev "OR", - :zip "97037"} - {:lat 40.281903, - :lon -76.52353099999999, - :house-number "1581", - :street "Horseshoe Pike", - :city "Lebanon", - :state-abbrev "PA", - :zip "17042"} - {:lat 34.9047695, - :lon -90.2370013, - :house-number "14036", - :street "Star Landing Road", - :city "Lake Cormorant", - :state-abbrev "MS", - :zip "38641"} - {:lat 39.514265, - :lon -83.53760559999999, - :house-number "5765-5981", - :street "U.S. 22", - :city "Washington Court House", - :state-abbrev "OH", - :zip "43160"} - {:lat 37.369013, - :lon -79.1272089, - :house-number "1986", - :street "Old Rustburg Road", - :city "Lynchburg", - :state-abbrev "VA", - :zip "24501"} - {:lat 32.3126203, - :lon -85.5588867, - :house-number "4615", - :street "County Road 16", - :city "Tuskegee", - :state-abbrev "AL", - :zip "36083"} - {:lat 40.9447254, - :lon -95.26528990000001, - :house-number "1601-1629", - :street "250 Street", - :city "Red Oak", - :state-abbrev "IA", - :zip "51566"} - {:lat 32.3376667, - :lon -80.528605, - :house-number "1", - :street "Whitams Island", - :city "Saint Helena Island", - :state-abbrev "SC", - :zip "29920"} - {:lat 30.1073029, - :lon -99.6336451, - :house-number "1-5", - :street "Texas 41", - :city "Mountain Home", - :state-abbrev "TX", - :zip "78058"} - {:lat 44.2163394, - :lon -84.8966808, - :house-number "8001-9999", - :street "East Finkle Road", - :city "Falmouth", - :state-abbrev "MI", - :zip "49632"} - {:lat 37.4752539, - :lon -91.1805078, - :house-number "1272", - :street "County Road 900", - :city "Bunker", - :state-abbrev "MO", - :zip "63629"} - {:lat 43.1691463, - :lon -95.1268561, - :house-number "2301-2307", - :street "East 30th Street", - :city "Spencer", - :state-abbrev "IA", - :zip "51301"} - {:lat 40.6847803, - :lon -86.688831, - :house-number "900", - :street "East North Street", - :city "Delphi", - :state-abbrev "IN", - :zip "46923"} - {:lat 54.8488896, - :lon -163.4073178, - :house-number "180", - :street "Unimak Drive", - :city "False Pass", - :state-abbrev "AK", - :zip "99583"} - {:lat 34.0070049, - :lon -91.5941429, - :house-number "755", - :street "Cades Lane", - :city "Gould", - :state-abbrev "AR", - :zip "71643"} - {:lat 38.5792025, - :lon -106.887352, - :house-number "321", - :street "Magnolia", - :city "Gunnison", - :state-abbrev "CO", - :zip "81230"} - {:lat 46.1661469, - :lon -106.2884395, - :house-number "895", - :street "Sweeney Creek Road", - :city "Rosebud", - :state-abbrev "MT", - :zip "59347"} - {:lat 29.1169075, - :lon -96.3296448, - :house-number "505", - :street "South FM 441 Road", - :city "Louise", - :state-abbrev "TX", - :zip "77455"} - {:lat 33.8299358, - :lon -93.5065874, - :house-number "593", - :street "Hempstead 207 Road", - :city "Prescott", - :state-abbrev "AR", - :zip "71857"} - {:lat 48.133432, - :lon -96.9065397, - :house-number "39583", - :street "180th Street Northwest", - :city "Warren", - :state-abbrev "MN", - :zip "56762"} - {:lat 40.041491, - :lon -85.717243, - :house-number "2602", - :street "Enterprise Drive", - :city "Anderson", - :state-abbrev "IN", - :zip "46013"} - {:lat 48.0997898, - :lon -103.2000809, - :house-number "11851", - :street "46th Street Northwest", - :city "Watford City", - :state-abbrev "ND", - :zip "58854"} - {:lat 31.1115134, - :lon -89.6005565, - :house-number "287", - :street "Tatum Salt Dome Road", - :city "Lumberton", - :state-abbrev "MS", - :zip "39455"} - {:lat 58.4085, - :lon -134.7549999, - :house-number "17900", - :street "Glacier Highway", - :city "Juneau", - :state-abbrev "AK", - :zip "99801"} - {:lat 46.701241, - :lon -119.5692451, - :house-number "7026", - :street "Mallard Drive Southeast", - :city "Warden", - :state-abbrev "WA", - :zip "98857"} - {:lat 29.9892952, - :lon -93.08805, - :house-number "1019-1299", - :street "Louisiana 27", - :city "Bell City", - :state-abbrev "LA", - :zip "70630"} - {:lat 41.676099, - :lon -73.378096, - :house-number "106", - :street "New Preston Hill Road", - :city "Washington", - :state-abbrev "CT", - :zip "06777"} - {:lat 46.1091442, - :lon -107.0786299, - :house-number "173", - :street "Bear Creek Road", - :city "Hysham", - :state-abbrev "MT", - :zip "59038"} - {:lat 35.65109899999999, - :lon -91.54011799999999, - :house-number "560", - :street "Goodie Creek Road", - :city "Rosie", - :state-abbrev "AR", - :zip "72571"} - {:lat 32.761235, - :lon -100.8384885, - :house-number "5000", - :street "East County Road 126", - :city "Snyder", - :state-abbrev "TX", - :zip "79549"} - {:lat 43.6587436, - :lon -97.7557446, - :house-number "42259", - :street "257th Street", - :city "Alexandria", - :state-abbrev "SD", - :zip "57311"} - {:lat 48.1777173, - :lon -105.7346406, - :house-number "5689", - :street "Road 1078", - :city "Wolf Point", - :state-abbrev "MT", - :zip "59201"} - {:lat 38.7081942, - :lon -105.1350515, - :house-number "114", - :street "Cedar Street", - :city "Victor", - :state-abbrev "CO", - :zip "80860"} - {:lat 44.0779761, - :lon -86.2130723, - :house-number "2705", - :street "East Townline Road", - :city "Free Soil", - :state-abbrev "MI", - :zip "49411"} - {:lat 38.938575, - :lon -77.883971, - :house-number "2518", - :street "Rectortown Road", - :city "Marshall", - :state-abbrev "VA", - :zip "20115"} - {:lat 38.4676563, - :lon -102.678174, - :house-number "44000-46998", - :street "Colorado 96", - :city "Eads", - :state-abbrev "CO", - :zip "81036"} - {:lat 35.03465, - :lon -113.681162, - :house-number "11735", - :street "Desert Fire Trail", - :city "Kingman", - :state-abbrev "AZ", - :zip "86401"} - {:lat 29.165482, - :lon -95.8897788, - :house-number "7500", - :street "FM 1728", - :city "Pledger", - :state-abbrev "TX", - :zip "77468"} - {:lat 45.2686766, - :lon -112.508021, - :house-number "3663", - :street "Stone Creek Road", - :city "Dillon", - :state-abbrev "MT", - :zip "59725"} - {:lat 38.5078724, - :lon -82.7571526, - :house-number "4478", - :street "Indian Run Road", - :city "Flatwoods", - :state-abbrev "KY", - :zip "41139"} - {:lat 47.3844573, - :lon -98.400517, - :house-number "10064", - :street "2nd Street Southeast", - :city "Sutton", - :state-abbrev "ND", - :zip "58484"} - {:lat 29.732441, - :lon -91.5441532, - :house-number "9071", - :street "Highway 182", - :city "Franklin", - :state-abbrev "LA", - :zip "70538"} - {:lat 37.8210517, - :lon -90.56398279999999, - :house-number "1700-1710", - :street "Ellis Road", - :city "Park Hills", - :state-abbrev "MO", - :zip "63601"} - {:lat 37.5612611, - :lon -85.9417637, - :house-number "128", - :street "Shady Bower Lane", - :city "Sonora", - :state-abbrev "KY", - :zip "42776"} - {:lat 37.4489193, - :lon -89.2712877, - :house-number "309-311", - :street "South Locust Street", - :city "Jonesboro", - :state-abbrev "IL", - :zip "62952"} - {:lat 29.4095736, - :lon -82.12125309999999, - :house-number "1579", - :street "Northeast 178th Place", - :city "Citra", - :state-abbrev "FL", - :zip "32113"} - {:lat 41.851276, - :lon -71.623779, - :house-number "164", - :street "Rocky Hill Road", - :city "Scituate", - :state-abbrev "RI", - :zip "02857"} - {:lat 36.8993552, - :lon -93.37103309999999, - :house-number "955-1163", - :street "V-20 C", - :city "Highlandville", - :state-abbrev "MO", - :zip "65669"} - {:lat 45.871341, - :lon -118.7664048, - :house-number "81140-81160", - :street "County 983 Road", - :city "Helix", - :state-abbrev "OR", - :zip "97835"} - {:lat 42.42348260000001, - :lon -91.4372509, - :house-number "1831", - :street "255th Street", - :city "Manchester", - :state-abbrev "IA", - :zip "52057"} - {:lat 36.8977589, - :lon -97.2649208, - :house-number "6945", - :street "North O Street", - :city "Braman", - :state-abbrev "OK", - :zip "74632"} - {:lat 37.75066169999999, - :lon -121.4455218, - :house-number "1600-1620", - :street "Hurley Court", - :city "Tracy", - :state-abbrev "CA", - :zip "95376"} - {:lat 46.2182794, - :lon -85.8061999, - :house-number "W18444", - :street "Hoffy Road", - :city "Germfask", - :state-abbrev "MI", - :zip "49836"} - {:lat 41.75712559999999, - :lon -96.7505654, - :house-number "1476-1498", - :street "B Road", - :city "West Point", - :state-abbrev "NE", - :zip "68788"} - {:lat 26.764708, - :lon -81.20095599999999, - :house-number "340", - :street "Witt Road", - :city "Clewiston", - :state-abbrev "FL", - :zip "33440"} - {:lat 42.69133370000001, - :lon -105.3532553, - :house-number "323-355", - :street "Irvine Road", - :city "Douglas", - :state-abbrev "WY", - :zip "82633"} - {:lat 34.2297964, - :lon -96.58023569999999, - :house-number "1002", - :street "Condon Grove Lane", - :city "Milburn", - :state-abbrev "OK", - :zip "73450"} - {:lat 42.19197279999999, - :lon -96.9166444, - :house-number "58101-58155", - :street "853rd Road", - :city "Wakefield", - :state-abbrev "NE", - :zip "68784"} - {:lat 44.8080149, - :lon -90.2971223, - :house-number "1095", - :street "Buxton Road", - :city "Spencer", - :state-abbrev "WI", - :zip "54479"} - {:lat 32.1473028, - :lon -96.08273989999999, - :house-number "350", - :street "North League Line Road", - :city "Trinidad", - :state-abbrev "TX", - :zip "75163"} - {:lat 44.1879289, - :lon -94.9584548, - :house-number "15436", - :street "County Highway 5", - :city "Springfield", - :state-abbrev "MN", - :zip "56087"} - {:lat 42.40922399999999, - :lon -78.112782, - :house-number "6642", - :street "Shongo Valley Road", - :city "Fillmore", - :state-abbrev "NY", - :zip "14735"} - {:lat 36.061302, - :lon -79.71980200000002, - :house-number "3435", - :street "McConnell Road", - :city "Greensboro", - :state-abbrev "NC", - :zip "27405"} - {:lat 38.961543, - :lon -85.862494, - :house-number "192-198", - :street "Agrico Lane", - :city "Seymour", - :state-abbrev "IN", - :zip "47274"} - {:lat 39.8072259, - :lon -120.3030839, - :house-number "86204", - :street "California 70", - :city "Beckwourth", - :state-abbrev "CA", - :zip "96129"} - {:lat 37.5284349, - :lon -87.6383283, - :house-number "1422", - :street "Kentucky 138", - :city "Dixon", - :state-abbrev "KY", - :zip "42409"} - {:lat 38.327386, - :lon -98.5166596, - :house-number "227-257", - :street "Southeast 140 Avenue", - :city "Ellinwood", - :state-abbrev "KS", - :zip "67526"} - {:lat 63.63418100000001, - :lon -148.7870271, - :house-number "229", - :street "George Parks Highway", - :city "Denali National Park and Preserve", - :state-abbrev "AK", - :zip "99755"} - {:lat 46.7353089, - :lon -104.7412436, - :house-number "1602", - :street "Cabin Creek Road", - :city "Fallon", - :state-abbrev "MT", - :zip "59326"} - {:lat 35.4267215, - :lon -94.4967037, - :house-number "201", - :street "Lennington Street", - :city "Roland", - :state-abbrev "OK", - :zip "74954"} - {:lat 43.36129890000001, - :lon -116.8722702, - :house-number "13684", - :street "US Highway 95", - :city "Marsing", - :state-abbrev "ID", - :zip "83639"} - {:lat 32.0403894, - :lon -95.5530392, - :house-number "3930", - :street "County Road 312", - :city "Frankston", - :state-abbrev "TX", - :zip "75763"} - {:lat 35.0765497, - :lon -106.5560287, - :house-number "8101-8199", - :street "Chico Road Northeast", - :city "Albuquerque", - :state-abbrev "NM", - :zip "87108"} - {:lat 39.1612492, - :lon -89.81132269999999, - :house-number "18240", - :street "Quarry Road", - :city "Gillespie", - :state-abbrev "IL", - :zip "62033"} - {:lat 40.5954602, - :lon -78.5604488, - :house-number "201", - :street "Dysart Drive", - :city "Dysart", - :state-abbrev "PA", - :zip "16636"} - {:lat 46.55166, - :lon -116.021097, - :house-number "2869", - :street "Bashaw Road", - :city "Orofino", - :state-abbrev "ID", - :zip "83544"} - {:lat 36.8637617, - :lon -94.4574351, - :house-number "14119", - :street "Haven Lane", - :city "Neosho", - :state-abbrev "MO", - :zip "64850"} - {:lat 38.0967399, - :lon -118.990192, - :house-number "9954", - :street "California 167", - :city "Bridgeport", - :state-abbrev "CA", - :zip "93517"} - {:lat 37.2872133, - :lon -84.9102649, - :house-number "1217", - :street "Gosser Ridge Road", - :city "Liberty", - :state-abbrev "KY", - :zip "42539"} - {:lat 32.9386966, - :lon -83.3117592, - :house-number "1748", - :street "Fred Hall Road", - :city "Gordon", - :state-abbrev "GA", - :zip "31031"} - {:lat 48.7637566, - :lon -102.1365474, - :house-number "53401-54799", - :street "506th Avenue Northwest", - :city "Bowbells", - :state-abbrev "ND", - :zip "58721"} - {:lat 35.3441291, - :lon -106.207004, - :house-number "2327-2391", - :street "Turquoise Trail", - :city "Los Cerrillos", - :state-abbrev "NM", - :zip "87010"} - {:lat 47.0220841, - :lon -111.6467656, - :house-number "2265", - :street "Adel Lane", - :city "Cascade", - :state-abbrev "MT", - :zip "59421"} - {:lat 47.761038, - :lon -100.7330401, - :house-number "601-681", - :street "24th Street Northwest", - :city "Butte", - :state-abbrev "ND", - :zip "58723"} - {:lat 34.991869, - :lon -120.187257, - :house-number "752", - :street "Tepusquet Road", - :city "Santa Maria", - :state-abbrev "CA", - :zip "93454"} - {:lat 42.5428125, - :lon -92.0625377, - :house-number "1678-1684", - :street "Baxter Avenue", - :city "Jesup", - :state-abbrev "IA", - :zip "50648"} - {:lat 30.7372104, - :lon -89.0382218, - :house-number "1085-1249", - :street "Wire Road East", - :city "Perkinston", - :state-abbrev "MS", - :zip "39573"} - {:lat 46.259491, - :lon -119.473502, - :house-number "31703", - :street "North River Road Way", - :city "Benton City", - :state-abbrev "WA", - :zip "99320"} - {:lat 45.65729959999999, - :lon -111.2469468, - :house-number "10722", - :street "Pine Butte Road", - :city "Bozeman", - :state-abbrev "MT", - :zip "59718"} - {:lat 36.6521744, - :lon -82.1788784, - :house-number "9438", - :street "Goose Creek Road", - :city "Bristol", - :state-abbrev "VA", - :zip "24202"} - {:lat 28.95668, - :lon -98.4589081, - :house-number "1393", - :street "Coughran Road", - :city "Pleasanton", - :state-abbrev "TX", - :zip "78064"} - {:lat 34.689707, - :lon -112.329328, - :house-number "10150", - :street "North Antelope Meadows Drive", - :city "Prescott Valley", - :state-abbrev "AZ", - :zip "86315"} - {:lat 42.7290184, - :lon -85.65195179999999, - :house-number "4260-4264", - :street "Cloverfield Court", - :city "Wayland", - :state-abbrev "MI", - :zip "49348"} - {:lat 41.1628398, - :lon -91.5400306, - :house-number "1004", - :street "Henry-Washington Road", - :city "Crawfordsville", - :state-abbrev "IA", - :zip "52621"} - {:lat 32.8014353, - :lon -97.2434668, - :house-number "2524", - :street "Minnis Drive", - :city "Fort Worth", - :state-abbrev "TX", - :zip "76117"} - {:lat 35.5192539, - :lon -96.793336, - :house-number "103615", - :street "South 3490 Road", - :city "Prague", - :state-abbrev "OK", - :zip "74864"} - {:lat 61.3830338, - :lon -145.2370339, - :house-number "56", - :street "Richardson Highway", - :city "Valdez", - :state-abbrev "AK", - :zip "99686"} - {:lat 31.951665, - :lon -85.0065432, - :house-number "1704", - :street "Georgia 27", - :city "Georgetown", - :state-abbrev "GA", - :zip "39854"} - {:lat 35.3371225, - :lon -89.548907, - :house-number "2785", - :street "Porter Road", - :city "Mason", - :state-abbrev "TN", - :zip "38049"} - {:lat 35.702655, - :lon -119.7611477, - :house-number "1667", - :street "Shelco Road", - :city "Lost Hills", - :state-abbrev "CA", - :zip "93249"} - {:lat 43.963342, - :lon -88.031961, - :house-number "23933", - :street "Point Creek Road", - :city "Kiel", - :state-abbrev "WI", - :zip "53042"} - {:lat 47.4609631, - :lon -111.3595861, - :house-number "4700-5230", - :street "31st Street Southwest", - :city "Great Falls", - :state-abbrev "MT", - :zip "59404"} - {:lat 40.1041264, - :lon -94.8880025, - :house-number "1507", - :street "County Road 34", - :city "Bolckow", - :state-abbrev "MO", - :zip "64427"} - {:lat 46.9547514, - :lon -94.328178, - :house-number "4081-4157", - :street "McKeown Lake Road Northwest", - :city "Hackensack", - :state-abbrev "MN", - :zip "56452"} - {:lat 41.2161147, - :lon -84.5251529, - :house-number "15492", - :street "Road 218", - :city "Cecil", - :state-abbrev "OH", - :zip "45821"} - {:lat 31.1858, - :lon -85.54050000000001, - :house-number "441", - :street "Pilgrims Rest Road", - :city "Slocomb", - :state-abbrev "AL", - :zip "36375"} - {:lat 39.5623999, - :lon -82.899913, - :house-number "7117", - :street "Tarlton Road", - :city "Circleville", - :state-abbrev "OH", - :zip "43113"} - {:lat 61.51703819999999, - :lon -148.7885501, - :house-number "20000", - :street "East Plumley Road", - :city "Palmer", - :state-abbrev "AK", - :zip "99645"} - {:lat 46.176571, - :lon -102.2612163, - :house-number "1928-1968", - :street "13th Avenue Northeast", - :city "Mott", - :state-abbrev "ND", - :zip "58646"} - {:lat 47.380622, - :lon -119.385335, - :house-number "4917", - :street "Road 20 Northeast", - :city "Soap Lake", - :state-abbrev "WA", - :zip "98851"} - {:lat 33.6260388, - :lon -82.5395871, - :house-number "1055", - :street "Smith Mill Road", - :city "Thomson", - :state-abbrev "GA", - :zip "30824"} - {:lat 31.4103163, - :lon -83.5762767, - :house-number "675", - :street "Isabella-Nashville Road", - :city "Tifton", - :state-abbrev "GA", - :zip "31793"} - {:lat 44.306777, - :lon -85.9339515, - :house-number "18310", - :street "North Coates Highway", - :city "Brethren", - :state-abbrev "MI", - :zip "49619"} - {:lat 61.52757399999999, - :lon -149.0397359, - :house-number "16385", - :street "Sullivan Avenue", - :city "Palmer", - :state-abbrev "AK", - :zip "99645"} - {:lat 38.9740243, - :lon -93.6939909, - :house-number "4766", - :street "Mock Road", - :city "Concordia", - :state-abbrev "MO", - :zip "64020"} - {:lat 46.8588589, - :lon -117.7999214, - :house-number "3898", - :street "Union Flat Creek Road", - :city "Endicott", - :state-abbrev "WA", - :zip "99125"} - {:lat 30.4446267, - :lon -90.4252506, - :house-number "211-231", - :street "North Rateau Road", - :city "Ponchatoula", - :state-abbrev "LA", - :zip "70454"} - {:lat 70.6355001, - :lon -160.043015, - :house-number "1355", - :street "Milikruak Street", - :city "Wainwright", - :state-abbrev "AK", - :zip "99782"} - {:lat 33.374294, - :lon -96.021456, - :house-number "1622", - :street "Farm to Market Road 1563", - :city "Wolfe City", - :state-abbrev "TX", - :zip "75496"} - {:lat 44.9507935, - :lon -105.634956, - :house-number "116-118", - :street "Weischedel Road", - :city "Recluse", - :state-abbrev "WY", - :zip "82725"} - {:lat 41.6255455, - :lon -92.124358, - :house-number "2625", - :street "J Avenue", - :city "Williamsburg", - :state-abbrev "IA", - :zip "52361"} - {:lat 37.0615106, - :lon -83.92517699999999, - :house-number "1547", - :street "Blackwater Road", - :city "London", - :state-abbrev "KY", - :zip "40744"} - {:lat 30.0033359, - :lon -94.6902803, - :house-number "1150", - :street "County Road 118", - :city "Liberty", - :state-abbrev "TX", - :zip "77575"} - {:lat 40.1186724, - :lon -94.4976959, - :house-number "4130", - :street "470 Road", - :city "King City", - :state-abbrev "MO", - :zip "64463"} - {:lat 35.1502163, - :lon -90.5974762, - :house-number "344-654", - :street "3 Way Road West", - :city "Heth", - :state-abbrev "AR", - :zip "72346"} - {:lat 32.6073483, - :lon -105.4085629, - :house-number "4408", - :street "Owen Prather Highway", - :city "Piñon", - :state-abbrev "NM", - :zip "88344"} - {:lat 32.3870464, - :lon -82.37761669999999, - :house-number "755", - :street "Georgia 46", - :city "Lyons", - :state-abbrev "GA", - :zip "30436"} - {:lat 39.6597005, - :lon -87.8228669, - :house-number "7393-7999", - :street "East 1300th Road", - :city "Paris", - :state-abbrev "IL", - :zip "61944"} - {:lat 42.13567, - :lon -78.173823, - :house-number "3149", - :street "Clair Carrier Road", - :city "Friendship", - :state-abbrev "NY", - :zip "14739"} - {:lat 34.13038179999999, - :lon -89.8219354, - :house-number "2824", - :street "County Road 41", - :city "Oakland", - :state-abbrev "MS", - :zip "38948"} - {:lat 35.5366119, - :lon -91.6837504, - :house-number "204-310", - :street "Staggs Drive", - :city "Pleasant Plains", - :state-abbrev "AR", - :zip "72568"} - {:lat 59.7496642, - :lon -161.9046606, - :house-number "101", - :street "Qanirtuuq Road", - :city "Quinhagak", - :state-abbrev "AK", - :zip "99655"} - {:lat 36.8587898, - :lon -86.06222819999999, - :house-number "1149", - :street "State Park Road", - :city "Lucas", - :state-abbrev "KY", - :zip "42156"} - {:lat 44.51793199999999, - :lon -87.595322, - :house-number "N5334", - :street "Hemlock Lane", - :city "Kewaunee", - :state-abbrev "WI", - :zip "54216"} - {:lat 48.7385587, - :lon -113.2676118, - :house-number "309-605", - :street "Livermore Creek Road", - :city "Browning", - :state-abbrev "MT", - :zip "59417"} - {:lat 36.2050501, - :lon -91.950527, - :house-number "1325", - :street "Wideman Road", - :city "Oxford", - :state-abbrev "AR", - :zip "72565"} - {:lat 39.79783990000001, - :lon -98.91230829999999, - :house-number "150", - :street "Road", - :city "Athol", - :state-abbrev "KS", - :zip "66932"} - {:lat 32.3045134, - :lon -95.5923265, - :house-number "11332-11338", - :street "Sunrise Drive", - :city "Brownsboro", - :state-abbrev "TX", - :zip "75756"} - {:lat 33.966146, - :lon -83.170627, - :house-number "1709", - :street "Crawford Smithonia Road", - :city "Colbert", - :state-abbrev "GA", - :zip "30628"} - {:lat 36.0135077, - :lon -79.83430729999999, - :house-number "34100-34248", - :street "Interstate 85 Business", - :city "Greensboro", - :state-abbrev "NC", - :zip "27406"} - {:lat 31.3418073, - :lon -84.7140546, - :house-number "10993", - :street "Georgia 45", - :city "Damascus", - :state-abbrev "GA", - :zip "39841"} - {:lat 39.6956233, - :lon -93.3732881, - :house-number "21609", - :street "Liv 306", - :city "Hale", - :state-abbrev "MO", - :zip "64643"} - {:lat 59.8500631, - :lon -151.7150523, - :house-number "67945", - :street "Stoddard Avenue", - :city "Anchor Point", - :state-abbrev "AK", - :zip "99556"} - {:lat 38.8952257, - :lon -93.2806047, - :house-number "12851", - :street "Range Line Road", - :city "Houstonia", - :state-abbrev "MO", - :zip "65333"} - {:lat 33.2018945, - :lon -104.568924, - :house-number "242-254", - :street "Old Y O Crossing Road", - :city "Dexter", - :state-abbrev "NM", - :zip "88230"} - {:lat 41.029585, - :lon -93.153317, - :house-number "48908", - :street "310th Avenue", - :city "Russell", - :state-abbrev "IA", - :zip "50238"} - {:lat 26.9229735, - :lon -99.2503988, - :house-number "1803", - :street "Mier Avenue", - :city "Zapata", - :state-abbrev "TX", - :zip "78076"} - {:lat 32.249878, - :lon -88.457056, - :house-number "6311", - :street "Wallace Road", - :city "Meridian", - :state-abbrev "MS", - :zip "39301"} - {:lat 42.0848745, - :lon -90.31647199999999, - :house-number "5874", - :street "500th Avenue", - :city "Miles", - :state-abbrev "IA", - :zip "52064"} - {:lat 38.2981465, - :lon -95.24883729999999, - :house-number "27687", - :street "Northwest 1980th Road", - :city "Garnett", - :state-abbrev "KS", - :zip "66032"} - {:lat 37.8417416, - :lon -106.9536837, - :house-number "463", - :street "County Road 517", - :city "Creede", - :state-abbrev "CO", - :zip "81130"} - {:lat 43.0470953, - :lon -89.9016754, - :house-number "7866-8194", - :street "Byrn Grwyn Road", - :city "Barneveld", - :state-abbrev "WI", - :zip "53507"} - {:lat 43.8723442, - :lon -91.15840879999999, - :house-number "4000", - :street "South Kinney Coulee Road", - :city "Onalaska", - :state-abbrev "WI", - :zip "54650"} - {:lat 33.0377127, - :lon -116.8830116, - :house-number "100-112", - :street "Day Street", - :city "Ramona", - :state-abbrev "CA", - :zip "92065"} - {:lat 46.06438, - :lon -114.161997, - :house-number "2525", - :street "Rocky Mountain Road", - :city "Darby", - :state-abbrev "MT", - :zip "59829"} - {:lat 44.77638719999999, - :lon -69.3937615, - :house-number "407", - :street "Stinson Street", - :city "Pittsfield", - :state-abbrev "ME", - :zip "04967"} - {:lat 46.1943865, - :lon -109.4390499, - :house-number "105-361", - :street "Haase Road", - :city "Ryegate", - :state-abbrev "MT", - :zip "59074"} - {:lat 40.852284, - :lon -86.351699, - :house-number "1213", - :street "East Co Road 600 North", - :city "Logansport", - :state-abbrev "IN", - :zip "46947"} - {:lat 32.486036, - :lon -80.571241, - :house-number "43", - :street "Snapper Lane", - :city "Beaufort", - :state-abbrev "SC", - :zip "29907"} - {:lat 32.706736, - :lon -92.3541071, - :house-number "539", - :street "Mann Road", - :city "Downsville", - :state-abbrev "LA", - :zip "71234"} - {:lat 46.2678617, - :lon -84.752438, - :house-number "18640", - :street "U S F S 3136 Road", - :city "Rudyard", - :state-abbrev "MI", - :zip "49780"} - {:lat 44.0269686, - :lon -95.984054, - :house-number "1201-1239", - :street "40th Avenue", - :city "Lake Wilson", - :state-abbrev "MN", - :zip "56151"} - {:lat 39.5259232, - :lon -118.6169201, - :house-number "8400", - :street "Austin Road", - :city "Fallon", - :state-abbrev "NV", - :zip "89406"} - {:lat 64.86752899999999, - :lon -147.660289, - :house-number "271", - :street "Peters Road", - :city "Fairbanks", - :state-abbrev "AK", - :zip "99712"} - {:lat 37.499158, - :lon -120.553747, - :house-number "3728", - :street "Turlock Road", - :city "Snelling", - :state-abbrev "CA", - :zip "95369"} - {:lat 42.5105867, - :lon -96.0165025, - :house-number "1358-1398", - :street "Ida Avenue", - :city "Moville", - :state-abbrev "IA", - :zip "51039"} - {:lat 32.1078929, - :lon -94.4963762, - :house-number "893", - :street "County Road 151", - :city "Carthage", - :state-abbrev "TX", - :zip "75633"} - {:lat 29.56428, - :lon -98.71687899999999, - :house-number "11220", - :street "Indian Trail", - :city "Helotes", - :state-abbrev "TX", - :zip "78023"} - {:lat 46.871891, - :lon -109.003212, - :house-number "13160", - :street "Surenuff Road", - :city "Forest Grove", - :state-abbrev "MT", - :zip "59441"} - {:lat 37.4667781, - :lon -92.8629958, - :house-number "2059-2087", - :street "Country Trails Road", - :city "Conway", - :state-abbrev "MO", - :zip "65632"} - {:lat 44.6845383, - :lon -116.3997104, - :house-number "2368-2380", - :street "National Forest Development Road 199", - :city "Council", - :state-abbrev "ID", - :zip "83612"} - {:lat 30.0520353, - :lon -95.5334307, - :house-number "19020", - :street "Doerre Road", - :city "Spring", - :state-abbrev "TX", - :zip "77379"} - {:lat 35.250475, - :lon -91.03065199999999, - :house-number "93", - :street "U.S. Highway 64", - :city "McCrory", - :state-abbrev "AR", - :zip "72101"} - {:lat 38.809142, - :lon -85.04777299999999, - :house-number "325", - :street "Plum Creek Road", - :city "Vevay", - :state-abbrev "IN", - :zip "47043"} - {:lat 44.6804526, - :lon -98.13950709999999, - :house-number "18622", - :street "404th Avenue", - :city "Hitchcock", - :state-abbrev "SD", - :zip "57348"} - {:lat 40.7801538, - :lon -98.8175242, - :house-number "10870", - :street "Range Road", - :city "Gibbon", - :state-abbrev "NE", - :zip "68840"} - {:lat 40.9412188, - :lon -104.0093035, - :house-number "64920", - :street "County Road 111", - :city "Grover", - :state-abbrev "CO", - :zip "80729"} - {:lat 46.6835719, - :lon -91.49065929999999, - :house-number "76525", - :street "Middleman Road", - :city "Iron River", - :state-abbrev "WI", - :zip "54847"} - {:lat 46.3307976, - :lon -107.0807389, - :house-number "1360", - :street "Mission Valley Road", - :city "Hysham", - :state-abbrev "MT", - :zip "59038"} - {:lat 41.1401692, - :lon -80.383175, - :house-number "4529", - :street "New Castle Road", - :city "New Wilmington", - :state-abbrev "PA", - :zip "16142"} - {:lat 38.5239585, - :lon -90.2220954, - :house-number "108", - :street "Coulter Road", - :city "Dupo", - :state-abbrev "IL", - :zip "62240"} - {:lat 35.7222386, - :lon -95.5312242, - :house-number "2300", - :street "South 114th Street West", - :city "Muskogee", - :state-abbrev "OK", - :zip "74401"} - {:lat 39.4734539, - :lon -77.850335, - :house-number "1261", - :street "Cedar Lane", - :city "Shepherdstown", - :state-abbrev "WV", - :zip "25443"} - {:lat 47.6225856, - :lon -94.76550069999999, - :house-number "15753", - :street "Gull Lake Loop Road Northeast", - :city "Bemidji", - :state-abbrev "MN", - :zip "56601"} - {:lat 47.8606576, - :lon -97.8522739, - :house-number "4758-4798", - :street "13th Avenue Northeast", - :city "Larimore", - :state-abbrev "ND", - :zip "58251"} - {:lat 42.9046042, - :lon -71.5083034, - :house-number "16", - :street "Cottage Walk", - :city "Bedford", - :state-abbrev "NH", - :zip "03110"} - {:lat 38.0211479, - :lon -84.579594, - :house-number "1501", - :street "Beaumont Centre Lane", - :city "Lexington", - :state-abbrev "KY", - :zip "40513"} - {:lat 37.4740047, - :lon -90.7621156, - :house-number "27645", - :street "Missouri 21", - :city "Lesterville", - :state-abbrev "MO", - :zip "63654"} - {:lat 45.0111897, - :lon -122.9340654, - :house-number "6225-6403", - :street "62nd Avenue Northeast", - :city "Salem", - :state-abbrev "OR", - :zip "97305"} - {:lat 30.34925699999999, - :lon -81.810532, - :house-number "9278", - :street "Derby Acres Lane", - :city "Jacksonville", - :state-abbrev "FL", - :zip "32220"} - {:lat 42.9353519, - :lon -96.8425834, - :house-number "30716", - :street "Greenfield Road", - :city "Burbank", - :state-abbrev "SD", - :zip "57010"} - {:lat 36.4962399, - :lon -76.1600948, - :house-number "193-195", - :street "Eagle Creek Road", - :city "Moyock", - :state-abbrev "NC", - :zip "27958"} - {:lat 38.237254, - :lon -86.96439, - :house-number "9426", - :street "U.S. 231", - :city "Huntingburg", - :state-abbrev "IN", - :zip "47542"} - {:lat 43.557459, - :lon -84.6285341, - :house-number "5782", - :street "South Chippewa Road", - :city "Shepherd", - :state-abbrev "MI", - :zip "48883"} - {:lat 36.9634275, - :lon -84.72491509999999, - :house-number "148", - :street "Cedar Bluff Drive", - :city "Monticello", - :state-abbrev "KY", - :zip "42633"} - {:lat 45.3888078, - :lon -98.7651605, - :house-number "37119", - :street "138th Street", - :city "Mina", - :state-abbrev "SD", - :zip "57451"} - {:lat 48.5907054, - :lon -107.675887, - :house-number "2030", - :street "River Road", - :city "Malta", - :state-abbrev "MT", - :zip "59538"} - {:lat 46.7991253, - :lon -93.42379489999999, - :house-number "26989", - :street "560th Street", - :city "Palisade", - :state-abbrev "MN", - :zip "56469"} - {:lat 25.8698057, - :lon -81.173395, - :house-number "47201", - :street "Tamiami Trail East", - :city "Ochopee", - :state-abbrev "FL", - :zip "34141"} - {:lat 41.4273558, - :lon -89.5586106, - :house-number "19304", - :street "County Road 1550 East", - :city "Princeton", - :state-abbrev "IL", - :zip "61356"} - {:lat 33.3223142, - :lon -85.85702479999999, - :house-number "256", - :street "Mountain View Road", - :city "Ashland", - :state-abbrev "AL", - :zip "36251"} - {:lat 28.0595066, - :lon -82.32739939999999, - :house-number "11898", - :street "Tom Folsom Road", - :city "Thonotosassa", - :state-abbrev "FL", - :zip "33592"} - {:lat 48.686101, - :lon -104.741158, - :house-number "515", - :street "Welliver Road", - :city "Plentywood", - :state-abbrev "MT", - :zip "59254"} - {:lat 42.728378, - :lon -75.58789999999999, - :house-number "132", - :street "Merrifield Road", - :city "Earlville", - :state-abbrev "NY", - :zip "13332"} - {:lat 43.02555660000001, - :lon -123.3808739, - :house-number "2817", - :street "Boomer Hill Road", - :city "Myrtle Creek", - :state-abbrev "OR", - :zip "97457"} - {:lat 41.570531, - :lon -81.368055, - :house-number "7698", - :street "Hidden Valley Drive", - :city "Kirtland", - :state-abbrev "OH", - :zip "44094"} - {:lat 42.6449933, - :lon -99.955286, - :house-number "88413", - :street "426th Avenue", - :city "Ainsworth", - :state-abbrev "NE", - :zip "69210"} - {:lat 37.695751, - :lon -82.15174499999999, - :house-number "455", - :street "Lower Curry Branch Road", - :city "Delbarton", - :state-abbrev "WV", - :zip "25670"} - {:lat 40.425112, - :lon -75.74838, - :house-number "143", - :street "Lobachsville Road", - :city "Fleetwood", - :state-abbrev "PA", - :zip "19522"} - {:lat 29.2867264, - :lon -82.15236399999999, - :house-number "9370", - :street "US-441 South", - :city "Ocala", - :state-abbrev "FL", - :zip "34475"} - {:lat 34.1727417, - :lon -103.3267628, - :house-number "1680", - :street "South Roosevelt Road Q 1/2", - :city "Portales", - :state-abbrev "NM", - :zip "88130"} - {:lat 32.7751699, - :lon -81.56260879999999, - :house-number "3169", - :street "Brannens Bridge Road", - :city "Sylvania", - :state-abbrev "GA", - :zip "30467"} - {:lat 64.71566419999999, - :lon -148.5639821, - :house-number "328", - :street "George Parks Highway", - :city "Fairbanks", - :state-abbrev "AK", - :zip "99709"} - {:lat 43.816909, - :lon -75.421206, - :house-number "6459", - :street "Benton Road", - :city "Lowville", - :state-abbrev "NY", - :zip "13367"} - {:lat 48.4882457, - :lon -101.677415, - :house-number "4412-4472", - :street "74th Street Northwest", - :city "Carpio", - :state-abbrev "ND", - :zip "58725"} - {:lat 39.665793, - :lon -83.993715, - :house-number "812", - :street "Van Eaton Road", - :city "Xenia", - :state-abbrev "OH", - :zip "45385"} - {:lat 38.4189036, - :lon -86.25064789999999, - :house-number "14701-14899", - :street "West Pay Drive Northwest", - :city "Depauw", - :state-abbrev "IN", - :zip "47115"} - {:lat 45.6316468, - :lon -94.4986029, - :house-number "18995", - :street "Quaker Road", - :city "Albany", - :state-abbrev "MN", - :zip "56307"} - {:lat 45.79399, - :lon -120.9666936, - :house-number "1", - :street "Appaloosa Court", - :city "Centerville", - :state-abbrev "WA", - :zip "98613"} - {:lat 45.5686723, - :lon -89.3084379, - :house-number "3507", - :street "North Faust Lake Road", - :city "Rhinelander", - :state-abbrev "WI", - :zip "54501"} - {:lat 42.590495, - :lon -91.2598897, - :house-number "2754", - :street "137th Street", - :city "Earlville", - :state-abbrev "IA", - :zip "52041"} - {:lat 44.64831, - :lon -89.135053, - :house-number "N11196", - :street "Fisher Road", - :city "Iola", - :state-abbrev "WI", - :zip "54945"} - {:lat 36.7122979, - :lon -79.1152288, - :house-number "2150", - :street "Lewis Ferrell Road", - :city "South Boston", - :state-abbrev "VA", - :zip "24592"} - {:lat 44.136243, - :lon -93.1205566, - :house-number "5653", - :street "Northeast 46th Street", - :city "Owatonna", - :state-abbrev "MN", - :zip "55060"} - {:lat 31.8220068, - :lon -86.9211897, - :house-number "911", - :street "George Swamp Road", - :city "Pine Apple", - :state-abbrev "AL", - :zip "36768"} - {:lat 33.1818402, - :lon -93.25339939999999, - :house-number "70", - :street "Columbia Road 265", - :city "Magnolia", - :state-abbrev "AR", - :zip "71753"} - {:lat 32.4786567, - :lon -100.6970409, - :house-number "1-7", - :street "County Road 462", - :city "Loraine", - :state-abbrev "TX", - :zip "79532"} - {:lat 32.098464, - :lon -94.9092257, - :house-number "9798", - :street "County Road 471", - :city "Henderson", - :state-abbrev "TX", - :zip "75654"} - {:lat 47.6184996, - :lon -118.5427508, - :house-number "19602", - :street "7 Springs Dairy Road", - :city "Creston", - :state-abbrev "WA", - :zip "99117"} - {:lat 35.1566478, - :lon -103.9041753, - :house-number "6000-6398", - :street "Q R Az", - :city "Tucumcari", - :state-abbrev "NM", - :zip "88401"} - {:lat 37.3314546, - :lon -103.75748, - :house-number "21000-23904", - :street "County Road 153", - :city "Branson", - :state-abbrev "CO", - :zip "81027"} - {:lat 40.308699, - :lon -81.3817131, - :house-number "4325-4699", - :street "Watson Creek Road Southeast", - :city "Uhrichsville", - :state-abbrev "OH", - :zip "44683"} - {:lat 46.0871158, - :lon -90.2699255, - :house-number "1008-1164", - :street "County Highway FF", - :city "Butternut", - :state-abbrev "WI", - :zip "54514"} - {:lat 43.14766710000001, - :lon -97.7165775, - :house-number "330", - :street "Washington Street", - :city "Scotland", - :state-abbrev "SD", - :zip "57059"} - {:lat 36.9358921, - :lon -87.41141139999999, - :house-number "7330", - :street "Greenville Road", - :city "Hopkinsville", - :state-abbrev "KY", - :zip "42240"} - {:lat 48.51422489999999, - :lon -96.9010531, - :house-number "39296-39998", - :street "440th Street Northwest", - :city "Stephen", - :state-abbrev "MN", - :zip "56757"} - {:lat 48.6913934, - :lon -94.6949867, - :house-number "869", - :street "25th Avenue Southwest", - :city "Baudette", - :state-abbrev "MN", - :zip "56623"} - {:lat 40.1747307, - :lon -79.07656639999999, - :house-number "324", - :street "Spangler Road", - :city "Boswell", - :state-abbrev "PA", - :zip "15531"} - {:lat 35.630887, - :lon -86.95428799999999, - :house-number "834", - :street "Cranford Hollow Road", - :city "Columbia", - :state-abbrev "TN", - :zip "38401"} - {:lat 33.15448140000001, - :lon -111.5760606, - :house-number "899", - :street "West Daniel Road", - :city "San Tan Valley", - :state-abbrev "AZ", - :zip "85143"} - {:lat 63.88414579999999, - :lon -152.3099922, - :house-number "123", - :street "Airport way", - :city "Lake Minchumina", - :state-abbrev "AK", - :zip "99757"} - {:lat 47.0809647, - :lon -99.7587157, - :house-number "2290-2398", - :street "35th Avenue Southeast", - :city "Robinson", - :state-abbrev "ND", - :zip "58478"} - {:lat 40.493902, - :lon -82.893883, - :house-number "512", - :street "South Marion Street", - :city "Cardington", - :state-abbrev "OH", - :zip "43315"} - {:lat 40.8077553, - :lon -88.1398961, - :house-number "1700-1794", - :street "East 2800N Road", - :city "Piper City", - :state-abbrev "IL", - :zip "60959"} - {:lat 39.337351, - :lon -80.111975, - :house-number "539", - :street "Beverly Pike", - :city "Grafton", - :state-abbrev "WV", - :zip "26354"} - {:lat 40.88742420000001, - :lon -99.7145264, - :house-number "76299", - :street "Wiley Canyon Road", - :city "Lexington", - :state-abbrev "NE", - :zip "68850"} - {:lat 40.1645279, - :lon -90.5143044, - :house-number "20518", - :street "Carty Road", - :city "Rushville", - :state-abbrev "IL", - :zip "62681"} - {:lat 39.9229843, - :lon -94.17851689999999, - :house-number "12175-12179", - :street "Oval Avenue", - :city "Winston", - :state-abbrev "MO", - :zip "64689"} - {:lat 33.9322874, - :lon -99.0809671, - :house-number "15551-16213", - :street "Thaxton Pens Road", - :city "Electra", - :state-abbrev "TX", - :zip "76360"} - {:lat 36.035844, - :lon -77.00750239999999, - :house-number "748", - :street "Governors Road", - :city "Windsor", - :state-abbrev "NC", - :zip "27983"} - {:lat 40.6770766, - :lon -101.6959374, - :house-number "74868", - :street "330 Avenue", - :city "Imperial", - :state-abbrev "NE", - :zip "69033"} - {:lat 44.47460299999999, - :lon -93.2118069, - :house-number "32811", - :street "Garrett Avenue", - :city "Northfield", - :state-abbrev "MN", - :zip "55057"} - {:lat 44.711761, - :lon -89.80506299999999, - :house-number "1706", - :street "Fisher Lane", - :city "Mosinee", - :state-abbrev "WI", - :zip "54455"} - {:lat 30.9204382, - :lon -94.80944989999999, - :house-number "181", - :street "Rock Island Road", - :city "Moscow", - :state-abbrev "TX", - :zip "75960"} - {:lat 43.0761346, - :lon -83.8429441, - :house-number "5001-5129", - :street "Deland Road", - :city "Flushing", - :state-abbrev "MI", - :zip "48433"} - {:lat 64.67863249999999, - :lon -148.8758712, - :house-number "67", - :street "George Parks Highway", - :city "Nenana", - :state-abbrev "AK", - :zip "99760"} - {:lat 41.6846909, - :lon -74.4484736, - :house-number "102-270", - :street "Tempaloni Road", - :city "Ellenville", - :state-abbrev "NY", - :zip "12428"} - {:lat 40.2436957, - :lon -92.6596596, - :house-number "22971", - :street "Rye Creek Road", - :city "Kirksville", - :state-abbrev "MO", - :zip "63501"} - {:lat 28.59424, - :lon -96.735123, - :house-number "2412", - :street "Farm to Market 1679", - :city "Port Lavaca", - :state-abbrev "TX", - :zip "77979"} - {:lat 31.0430463, - :lon -81.9712313, - :house-number "13071", - :street "Winokur Road", - :city "Nahunta", - :state-abbrev "GA", - :zip "31553"} - {:lat 37.576548, - :lon -97.8057989, - :house-number "40502", - :street "West 63rd Street South", - :city "Cheney", - :state-abbrev "KS", - :zip "67025"} - {:lat 45.9095164, - :lon -91.19431709999999, - :house-number "10094", - :street "Campfire Circle", - :city "Hayward", - :state-abbrev "WI", - :zip "54843"} - {:lat 36.3751784, - :lon -92.2747564, - :house-number "7039", - :street "U.S. Highway 62", - :city "Mountain Home", - :state-abbrev "AR", - :zip "72653"} - {:lat 45.25263940000001, - :lon -102.3141182, - :house-number "18635", - :street "Usta Road", - :city "Faith", - :state-abbrev "SD", - :zip "57626"} - {:lat 36.8311004, - :lon -95.0253779, - :house-number "447534", - :street "East 130 Road", - :city "Bluejacket", - :state-abbrev "OK", - :zip "74333"} - {:lat 45.2530768, - :lon -90.206575, - :house-number "W3110", - :street "Wood Creek Avenue", - :city "Medford", - :state-abbrev "WI", - :zip "54451"} - {:lat 30.2842935, - :lon -95.5964412, - :house-number "4739-5421", - :street "Honea Egypt Road", - :city "Montgomery", - :state-abbrev "TX", - :zip "77316"} - {:lat 40.8287855, - :lon -98.0202961, - :house-number "1461-1493", - :street "West 10 Road", - :city "Aurora", - :state-abbrev "NE", - :zip "68818"} - {:lat 35.618299, - :lon -81.070032, - :house-number "3427", - :street "Joe Johnson Road", - :city "Catawba", - :state-abbrev "NC", - :zip "28609"} - {:lat 38.1662389, - :lon -95.68379499999999, - :house-number "901-999", - :street "Planter Road", - :city "Burlington", - :state-abbrev "KS", - :zip "66839"} - {:lat 38.3983727, - :lon -100.5071491, - :house-number "74", - :street "West Road 130", - :city "Dighton", - :state-abbrev "KS", - :zip "67839"} - {:lat 35.715097, - :lon -82.821319, - :house-number "650", - :street "Halfmoon Ridge", - :city "Hot Springs", - :state-abbrev "NC", - :zip "28743"} - {:lat 34.4179036, - :lon -102.0900064, - :house-number "2303-2349", - :street "County Road 526", - :city "Dimmitt", - :state-abbrev "TX", - :zip "79027"} - {:lat 37.4976312, - :lon -80.745508, - :house-number "204", - :street "Fritz Run Road", - :city "Forest Hill", - :state-abbrev "WV", - :zip "24935"} - {:lat 40.3159813, - :lon -96.3239148, - :house-number "61345", - :street "724 Road", - :city "Tecumseh", - :state-abbrev "NE", - :zip "68450"} - {:lat 37.7264844, - :lon -92.7464256, - :house-number "26496", - :street "Noland Drive", - :city "Lebanon", - :state-abbrev "MO", - :zip "65536"} - {:lat 46.2965995, - :lon -106.246808, - :house-number "2446", - :street "Cartersville Road", - :city "Rosebud", - :state-abbrev "MT", - :zip "59347"} - {:lat 27.4748437, - :lon -82.3284395, - :house-number "20890", - :street "Florida 64", - :city "Bradenton", - :state-abbrev "FL", - :zip "34212"} - {:lat 31.5021188, - :lon -88.2518348, - :house-number "565", - :street "Ellis Jordan Sawmill Road", - :city "Chatom", - :state-abbrev "AL", - :zip "36518"} - {:lat 35.572079, - :lon -85.157082, - :house-number "701", - :street "Cherokee Ridge Road", - :city "Pikeville", - :state-abbrev "TN", - :zip "37367"} - {:lat 40.5912064, - :lon -85.77687759999999, - :house-number "5399", - :street "West Henderson Court", - :city "Marion", - :state-abbrev "IN", - :zip "46952"} - {:lat 29.693961, - :lon -97.501752, - :house-number "5142", - :street "Fm 1386 Harwood", - :city "Luling", - :state-abbrev "TX", - :zip "78648"} - {:lat 31.2508327, - :lon -103.9209099, - :house-number "300", - :street "County Road 229", - :city "Toyah", - :state-abbrev "TX", - :zip "79785"} - {:lat 44.7851097, - :lon -91.92887069999999, - :house-number "4400-4410", - :street "290th Avenue", - :city "Menomonie", - :state-abbrev "WI", - :zip "54751"} - {:lat 42.8251985, - :lon -102.276917, - :house-number "6680", - :street "240th Lane", - :city "Gordon", - :state-abbrev "NE", - :zip "69343"} - {:lat 44.3388163, - :lon -83.42027739999999, - :house-number "1861", - :street "Davison Road", - :city "East Tawas", - :state-abbrev "MI", - :zip "48730"} - {:lat 62.327013, - :lon -150.0131199, - :house-number "21871", - :street "Hancock Street", - :city "Talkeetna", - :state-abbrev "AK", - :zip "99676"} - {:lat 39.9534575, - :lon -77.6047306, - :house-number "3199", - :street "Fox Hill Drive", - :city "Chambersburg", - :state-abbrev "PA", - :zip "17202"} - {:lat 31.6590638, - :lon -87.53021179999999, - :house-number "4000-5282", - :street "Mabien Lake Road", - :city "Franklin", - :state-abbrev "AL", - :zip "36444"} - {:lat 37.1956175, - :lon -103.9941708, - :house-number "13623-14999", - :street "County Road 127", - :city "Trinchera", - :state-abbrev "CO", - :zip "81081"} - {:lat 46.6717504, - :lon -116.7632031, - :house-number "1043-1057", - :street "Bethel Road", - :city "Troy", - :state-abbrev "ID", - :zip "83871"} - {:lat 39.7178499, - :lon -88.9226398, - :house-number "8417-9203", - :street "Walmsley Road", - :city "Macon", - :state-abbrev "IL", - :zip "62544"} - {:lat 39.30385709999999, - :lon -87.4732437, - :house-number "3675", - :street "West Evans Drive", - :city "Terre Haute", - :state-abbrev "IN", - :zip "47802"} - {:lat 31.7716629, - :lon -91.59559, - :house-number "974", - :street "Highway 567", - :city "Clayton", - :state-abbrev "LA", - :zip "71326"} - {:lat 42.4732457, - :lon -89.1208698, - :house-number "4002", - :street "Yale Bridge Road", - :city "Rockton", - :state-abbrev "IL", - :zip "61072"} - {:lat 40.2688392, - :lon -96.9157705, - :house-number "26028", - :street "Southwest 142 Road", - :city "Beatrice", - :state-abbrev "NE", - :zip "68310"} - {:lat 44.2619463, - :lon -121.1254701, - :house-number "2899", - :street "Oregon 126", - :city "Redmond", - :state-abbrev "OR", - :zip "97756"} - {:lat 34.90789, - :lon -84.214333, - :house-number "100", - :street "Echo Valley Road", - :city "Morganton", - :state-abbrev "GA", - :zip "30560"} - {:lat 41.2484208, - :lon -89.6577463, - :house-number "10000-10484", - :street "County Road 700 North", - :city "Buda", - :state-abbrev "IL", - :zip "61314"} - {:lat 31.6778338, - :lon -95.05052529999999, - :house-number "2200", - :street "County Road 2501", - :city "Alto", - :state-abbrev "TX", - :zip "75925"} - {:lat 34.788219, - :lon -87.5892764, - :house-number "1815", - :street "River Road", - :city "Muscle Shoals", - :state-abbrev "AL", - :zip "35661"} - {:lat 44.0660442, - :lon -96.8579036, - :house-number "46756", - :street "229th Street", - :city "Colman", - :state-abbrev "SD", - :zip "57017"} - {:lat 35.1008912, - :lon -93.7577212, - :house-number "602-606", - :street "Catlett Lane", - :city "Magazine", - :state-abbrev "AR", - :zip "72943"} - {:lat 42.0012328, - :lon -93.5811332, - :house-number "1813-1899", - :street "560th Avenue", - :city "Ames", - :state-abbrev "IA", - :zip "50010"} - {:lat 42.1118798, - :lon -86.07009219999999, - :house-number "84000-87998", - :street "54th Street", - :city "Decatur", - :state-abbrev "MI", - :zip "49045"} - {:lat 36.8677232, - :lon -84.2397771, - :house-number "813", - :street "Rosetown Church Road", - :city "Corbin", - :state-abbrev "KY", - :zip "40701"} - {:lat 44.77214, - :lon -73.4780679, - :house-number "207", - :street "Ashley Road", - :city "Plattsburgh", - :state-abbrev "NY", - :zip "12901"} - {:lat 45.3850832, - :lon -105.9586621, - :house-number "108", - :street "15 Mile Road", - :city "Sonnette", - :state-abbrev "MT", - :zip "59317"} - {:lat 40.29477139999999, - :lon -74.108205, - :house-number "7", - :street "Duke Court", - :city "Tinton Falls", - :state-abbrev "NJ", - :zip "07724"} - {:lat 29.796155, - :lon -96.17647799999999, - :house-number "972", - :street "FM 2187 Road", - :city "Sealy", - :state-abbrev "TX", - :zip "77474"} - {:lat 48.5031535, - :lon -122.8041454, - :house-number "134-372", - :street "Armitage Road", - :city "Blakely Island", - :state-abbrev "WA", - :zip "98222"} - {:lat 46.1421636, - :lon -102.9751943, - :house-number "1001-1075", - :street "17th Street Northwest", - :city "Reeder", - :state-abbrev "ND", - :zip "58649"} - {:lat 43.886111, - :lon -92.16992599999999, - :house-number "9241", - :street "County Road 10 Southeast", - :city "Chatfield", - :state-abbrev "MN", - :zip "55923"} - {:lat 30.9297181, - :lon -90.5465336, - :house-number "74212", - :street "Wyndotte Road", - :city "Kentwood", - :state-abbrev "LA", - :zip "70444"} - {:lat 38.920534, - :lon -94.180622, - :house-number "34604", - :street "East Hammond Road", - :city "Lone Jack", - :state-abbrev "MO", - :zip "64070"} - {:lat 39.8700699, - :lon -96.69368109999999, - :house-number "1000-1098", - :street "7th Road", - :city "Marysville", - :state-abbrev "KS", - :zip "66508"} - {:lat 41.69481, - :lon -87.57314679999999, - :house-number "11001", - :street "South Jeffery Avenue", - :city "Chicago", - :state-abbrev "IL", - :zip "60617"} - {:lat 32.2916274, - :lon -110.8232995, - :house-number "4645-4653", - :street "North Black Rock Place", - :city "Tucson", - :state-abbrev "AZ", - :zip "85750"} - {:lat 44.5974832, - :lon -85.6202182, - :house-number "2125", - :street "Clous Road", - :city "Kingsley", - :state-abbrev "MI", - :zip "49649"} - {:lat 33.43045740000001, - :lon -80.2814899, - :house-number "729", - :street "Ferguson Landing Way", - :city "Eutawville", - :state-abbrev "SC", - :zip "29048"} - {:lat 38.52552, - :lon -106.162496, - :house-number "7005", - :street "County Road 221", - :city "Salida", - :state-abbrev "CO", - :zip "81201"} - {:lat 41.1347313, - :lon -75.27136829999999, - :house-number "588", - :street "Cranberry Creek Road", - :city "Cresco", - :state-abbrev "PA", - :zip "18326"} - {:lat 44.805811, - :lon -106.662583, - :house-number "366", - :street "South R Buffalo Creek Road", - :city "Sheridan", - :state-abbrev "WY", - :zip "82801"} - {:lat 43.105184, - :lon -92.559314, - :house-number "3383", - :street "180th Street", - :city "Ionia", - :state-abbrev "IA", - :zip "50645"} - {:lat 40.171608, - :lon -88.153183, - :house-number "1652", - :street "County Road 2000 North", - :city "Urbana", - :state-abbrev "IL", - :zip "61802"} - {:lat 35.175917, - :lon -78.318257, - :house-number "163", - :street "Holiday Lane", - :city "Newton Grove", - :state-abbrev "NC", - :zip "28366"} - {:lat 37.26021110000001, - :lon -100.1818605, - :house-number "15454-15806", - :street "27 Road", - :city "Fowler", - :state-abbrev "KS", - :zip "67844"} - {:lat 44.3558952, - :lon -102.4925634, - :house-number "17938-17944", - :street "River Road", - :city "Wasta", - :state-abbrev "SD", - :zip "57791"} - {:lat 35.820176, - :lon -87.7719951, - :house-number "719", - :street "Lost Creek Road", - :city "Lobelville", - :state-abbrev "TN", - :zip "37097"} - {:lat 38.3082323, - :lon -89.4215566, - :house-number "15093-15749", - :street "Nixon Road", - :city "Nashville", - :state-abbrev "IL", - :zip "62263"} - {:lat 35.8872101, - :lon -78.68693979999999, - :house-number "3001", - :street "Howard Road", - :city "Raleigh", - :state-abbrev "NC", - :zip "27613"} - {:lat 34.1413335, - :lon -96.00751199999999, - :house-number "3078-3088", - :street "Mossy Lake Road", - :city "Bennington", - :state-abbrev "OK", - :zip "74723"} - {:lat 46.48775879999999, - :lon -90.99776849999999, - :house-number "63201-63227", - :street "Franciskovich Road", - :city "Mason", - :state-abbrev "WI", - :zip "54856"} - {:lat 37.7011357, - :lon -86.0150568, - :house-number "6626-7298", - :street "Long Grove Road", - :city "Elizabethtown", - :state-abbrev "KY", - :zip "42701"} - {:lat 41.77538, - :lon -92.8673, - :house-number "11449", - :street "North 67th Avenue East", - :city "Kellogg", - :state-abbrev "IA", - :zip "50135"} - {:lat 39.778217, - :lon -105.29644, - :house-number "26102", - :street "Golden Gate Canyon Road", - :city "Golden", - :state-abbrev "CO", - :zip "80403"} - {:lat 38.620669, - :lon -104.406894, - :house-number "22771", - :street "Sagebrush Lane", - :city "Colorado Springs", - :state-abbrev "CO", - :zip "80928"} - {:lat 42.287588, - :lon -104.8312269, - :house-number "262-278", - :street "Wendover Road", - :city "Guernsey", - :state-abbrev "WY", - :zip "82214"} - {:lat 46.6744302, - :lon -98.6800164, - :house-number "8453-8499", - :street "51st Street Southeast", - :city "Montpelier", - :state-abbrev "ND", - :zip "58472"} - {:lat 29.355971, - :lon -95.628168, - :house-number "22416", - :street "East Farm to Market 1462", - :city "Damon", - :state-abbrev "TX", - :zip "77430"} - {:lat 32.154442, - :lon -90.16464719999999, - :house-number "193-203", - :street "Zelma Lane", - :city "Florence", - :state-abbrev "MS", - :zip "39073"} - {:lat 36.3033271, - :lon -85.7327644, - :house-number "391", - :street "Frizzell Hollow Road", - :city "Gainesboro", - :state-abbrev "TN", - :zip "38562"} - {:lat 40.5445495, - :lon -100.6019921, - :house-number "38619", - :street "Road 740", - :city "Curtis", - :state-abbrev "NE", - :zip "69025"} - {:lat 26.249905, - :lon -97.63499449999999, - :house-number "21344", - :street "Krupala Road", - :city "Harlingen", - :state-abbrev "TX", - :zip "78550"} - {:lat 29.7187481, - :lon -97.91061529999999, - :house-number "4201", - :street "Old Lehman Road", - :city "Kingsbury", - :state-abbrev "TX", - :zip "78638"} - {:lat 35.114211, - :lon -81.7128989, - :house-number "1403", - :street "Chesnee Highway", - :city "Gaffney", - :state-abbrev "SC", - :zip "29341"} - {:lat 44.0759145, - :lon -89.10181390000001, - :house-number "4441", - :street "Buttercup Drive", - :city "Redgranite", - :state-abbrev "WI", - :zip "54970"} - {:lat 35.6755522, - :lon -78.76927649999999, - :house-number "3904", - :street "Summer Brook Drive", - :city "Apex", - :state-abbrev "NC", - :zip "27539"} - {:lat 44.7139688, - :lon -105.1429957, - :house-number "1311", - :street "Heald Road", - :city "Gillette", - :state-abbrev "WY", - :zip "82731"} - {:lat 36.397726, - :lon -88.528498, - :house-number "2285", - :street "Hunt Road", - :city "Cottage Grove", - :state-abbrev "TN", - :zip "38224"} - {:lat 38.136482, - :lon -80.790978, - :house-number "4182", - :street "Upper Anglins Creek", - :city "Mount Nebo", - :state-abbrev "WV", - :zip "26679"} - {:lat 43.9163659, - :lon -73.3899132, - :house-number "1800-1952", - :street "Lake Street", - :city "Shoreham", - :state-abbrev "VT", - :zip "05770"} - {:lat 35.0182001, - :lon -76.76226489999999, - :house-number "106", - :street "Creekside Lane", - :city "Oriental", - :state-abbrev "NC", - :zip "28571"} - {:lat 48.9439658, - :lon -104.8569088, - :house-number "295-299", - :street "Big Valley Road", - :city "Outlook", - :state-abbrev "MT", - :zip "59252"} - {:lat 40.4370093, - :lon -79.6483465, - :house-number "3475", - :street "Lake Ridge Drive", - :city "Murrysville", - :state-abbrev "PA", - :zip "15668"} - {:lat 40.711495, - :lon -74.929238, - :house-number "1", - :street "Spring Brook Lane", - :city "Glen Gardner", - :state-abbrev "NJ", - :zip "08826"} - {:lat 32.4896481, - :lon -99.7583769, - :house-number "3060", - :street "West Overland Trail", - :city "Abilene", - :state-abbrev "TX", - :zip "79603"} - {:lat 34.6214936, - :lon -78.91809429999999, - :house-number "166", - :street "Bessie", - :city "Lumberton", - :state-abbrev "NC", - :zip "28358"} - {:lat 42.2472548, - :lon -121.3915386, - :house-number "1517-4093", - :street "Bly Mountain Cutoff Road", - :city "Bonanza", - :state-abbrev "OR", - :zip "97623"} - {:lat 42.6855014, - :lon -76.2889201, - :house-number "1-661", - :street "Poverty Lane", - :city "Cortland", - :state-abbrev "NY", - :zip "13045"} - {:lat 44.5892651, - :lon -94.2497736, - :house-number "48824", - :street "250th Street", - :city "Gaylord", - :state-abbrev "MN", - :zip "55334"} - {:lat 47.272479, - :lon -116.3010206, - :house-number "4500", - :street "Gold Ridge Road", - :city "Saint Maries", - :state-abbrev "ID", - :zip "83861"} - {:lat 39.5533571, - :lon -97.2362132, - :house-number "801-881", - :street "30th Road", - :city "Clifton", - :state-abbrev "KS", - :zip "66937"} - {:lat 40.6242207, - :lon -93.865386, - :house-number "28765", - :street "U.S. 69", - :city "Davis City", - :state-abbrev "IA", - :zip "50065"} - {:lat 41.3743424, - :lon -94.6883993, - :house-number "1054-1076", - :street "190th Street", - :city "Anita", - :state-abbrev "IA", - :zip "50020"} - {:lat 41.00518599999999, - :lon -91.153881, - :house-number "136", - :street "Centennial Drive", - :city "Mediapolis", - :state-abbrev "IA", - :zip "52637"} - {:lat 36.8508045, - :lon -76.24297589999999, - :house-number "3448-3460", - :street "Trant Avenue", - :city "Norfolk", - :state-abbrev "VA", - :zip "23502"} - {:lat 35.5537, - :lon -85.543933, - :house-number "17162", - :street "Tennessee 8", - :city "McMinnville", - :state-abbrev "TN", - :zip "37110"} - {:lat 48.5054476, - :lon -113.9800674, - :house-number "113", - :street "Logan Lane", - :city "West Glacier", - :state-abbrev "MT", - :zip "59936"} - {:lat 44.6072478, - :lon -97.04753799999999, - :house-number "19149-19199", - :street "458th Avenue", - :city "Castlewood", - :state-abbrev "SD", - :zip "57223"} - {:lat 42.1794235, - :lon -90.1845962, - :house-number "6456-7908", - :street "Camp Creek Road", - :city "Savanna", - :state-abbrev "IL", - :zip "61074"} - {:lat 47.1812463, - :lon -94.1146838, - :house-number "3037-3167", - :street "104th Street Northeast", - :city "Remer", - :state-abbrev "MN", - :zip "56672"} - {:lat 63.5664678, - :lon -148.8142708, - :house-number "224", - :street "George Parks Highway", - :city "Denali National Park and Preserve", - :state-abbrev "AK", - :zip "99755"} - {:lat 42.54258309999999, - :lon -99.48099270000002, - :house-number "87743", - :street "450th Avenue", - :city "Bassett", - :state-abbrev "NE", - :zip "68714"} - {:lat 33.3037867, - :lon -91.7920324, - :house-number "244", - :street "Ashley Road", - :city "Hamburg", - :state-abbrev "AR", - :zip "71646"} - {:lat 36.607847, - :lon -96.9313729, - :house-number "939", - :street "80 Road", - :city "Ponca City", - :state-abbrev "OK", - :zip "74604"} - {:lat 46.466885, - :lon -120.392185, - :house-number "2141", - :street "Donald Wapato Road", - :city "Wapato", - :state-abbrev "WA", - :zip "98951"} - {:lat 42.6573649, - :lon -98.5648155, - :house-number "88536", - :street "496th Avenue", - :city "O'Neill", - :state-abbrev "NE", - :zip "68763"} - {:lat 40.97705, - :lon -104.282042, - :house-number "67125", - :street "County Road 83", - :city "Grover", - :state-abbrev "CO", - :zip "80729"} - {:lat 43.2851418, - :lon -115.1254857, - :house-number "9546", - :street "County Line Road", - :city "Hill City", - :state-abbrev "ID", - :zip "83337"} - {:lat 34.5428605, - :lon -88.07217159999999, - :house-number "2508", - :street "County Road 90", - :city "Red Bay", - :state-abbrev "AL", - :zip "35582"} - {:lat 33.3112533, - :lon -88.9530561, - :house-number "444", - :street "York Road", - :city "Sturgis", - :state-abbrev "MS", - :zip "39769"} - {:lat 38.290215, - :lon -75.33184709999999, - :house-number "8410", - :street "Ninepin Branch Road", - :city "Berlin", - :state-abbrev "MD", - :zip "21811"} - {:lat 48.3250683, - :lon -99.4660873, - :house-number "5317-5323", - :street "63rd Street Northeast", - :city "Leeds", - :state-abbrev "ND", - :zip "58346"} - {:lat 32.85935, - :lon -89.90156449999999, - :house-number "504-766", - :street "Burrell Road", - :city "Pickens", - :state-abbrev "MS", - :zip "39146"} - {:lat 32.0280591, - :lon -91.83638619999999, - :house-number "572-798", - :street "Louisiana 871", - :city "Winnsboro", - :state-abbrev "LA", - :zip "71295"} - {:lat 32.2929204, - :lon -88.7441599, - :house-number "5396-5492", - :street "Valley Road", - :city "Meridian", - :state-abbrev "MS", - :zip "39307"} - {:lat 43.318729, - :lon -85.77300699999999, - :house-number "1152", - :street "East 128th Street", - :city "Grant", - :state-abbrev "MI", - :zip "49327"} - {:lat 36.4598366, - :lon -85.3474748, - :house-number "100-312", - :street "Hunter Cove Road", - :city "Allons", - :state-abbrev "TN", - :zip "38541"} - {:lat 43.4705134, - :lon -88.0708804, - :house-number "7701-7759", - :street "Meadow Road", - :city "West Bend", - :state-abbrev "WI", - :zip "53090"} - {:lat 28.854199, - :lon -96.92175739999999, - :house-number "611", - :street "Foster Field Drive", - :city "Victoria", - :state-abbrev "TX", - :zip "77904"} - {:lat 39.27953000000001, - :lon -76.01646099999999, - :house-number "11020", - :street "Perkins Hill Road", - :city "Chestertown", - :state-abbrev "MD", - :zip "21620"} - {:lat 30.8716958, - :lon -81.5971895, - :house-number "155", - :street "Sunrise Drive", - :city "Woodbine", - :state-abbrev "GA", - :zip "31569"} - {:lat 33.9551463, - :lon -83.8301824, - :house-number "2200-2998", - :street "Harfield Court Southeast", - :city "Bethlehem", - :state-abbrev "GA", - :zip "30620"} - {:lat 44.5646367, - :lon -100.3644203, - :house-number "19400-19484", - :street "288th Avenue", - :city "Pierre", - :state-abbrev "SD", - :zip "57501"} - {:lat 40.46128239999999, - :lon -80.6199252, - :house-number "443", - :street "Fairview Heights Drive", - :city "Toronto", - :state-abbrev "OH", - :zip "43964"} - {:lat 46.22892179999999, - :lon -93.0031521, - :house-number "14256", - :street "Dahlstein Road", - :city "Finlayson", - :state-abbrev "MN", - :zip "55735"} - {:lat 28.171865, - :lon -99.040449, - :house-number "2065", - :street "Huajuco Lane Lasalle Co", - :city "Cotulla", - :state-abbrev "TX", - :zip "78014"} - {:lat 40.0093863, - :lon -105.4231668, - :house-number "6181", - :street "Sugarloaf Road", - :city "Boulder", - :state-abbrev "CO", - :zip "80302"} - {:lat 47.58456349999999, - :lon -106.1512125, - :house-number "1798", - :street "Montana 24", - :city "Circle", - :state-abbrev "MT", - :zip "59215"} - {:lat 40.510231, - :lon -104.974479, - :house-number "5317", - :street "South Co Road 3f", - :city "Fort Collins", - :state-abbrev "CO", - :zip "80528"} - {:lat 42.3615365, - :lon -71.3522355, - :house-number "8", - :street "Melody Lane", - :city "Wayland", - :state-abbrev "MA", - :zip "01778"} - {:lat 47.873104, - :lon -117.388124, - :house-number "23626", - :street "North Perry Road", - :city "Colbert", - :state-abbrev "WA", - :zip "99005"} - {:lat 41.2992808, - :lon -99.6296247, - :house-number "79369", - :street "Drive 439", - :city "Broken Bow", - :state-abbrev "NE", - :zip "68822"} - {:lat 35.8696915, - :lon -79.6465757, - :house-number "5346", - :street "Ramseur Julian Road", - :city "Liberty", - :state-abbrev "NC", - :zip "27298"} - {:lat 45.7665632, - :lon -99.2717316, - :house-number "34601-34605", - :street "112th Street", - :city "Eureka", - :state-abbrev "SD", - :zip "57437"} - {:lat 34.808124, - :lon -86.91013699999999, - :house-number "17400", - :street "Oakdale Road", - :city "Athens", - :state-abbrev "AL", - :zip "35613"} - {:lat 62.9602798, - :lon -143.353976, - :house-number "800", - :street "Tok Highway", - :city "Gakona", - :state-abbrev "AK", - :zip "99586"} - {:lat 44.005569, - :lon -69.320786, - :house-number "308", - :street "Cushing Road", - :city "Friendship", - :state-abbrev "ME", - :zip "04547"} - {:lat 41.3181687, - :lon -96.17180270000001, - :house-number "16500-16548", - :street "Bauman Circle", - :city "Omaha", - :state-abbrev "NE", - :zip "68116"} - {:lat 45.2915529, - :lon -96.4570057, - :house-number "101-151", - :street "Main Street", - :city "Big Stone City", - :state-abbrev "SD", - :zip "57216"} - {:lat 41.046256, - :lon -87.95983199999999, - :house-number "4777", - :street "West 5000 Road South", - :city "Kankakee", - :state-abbrev "IL", - :zip "60901"} - {:lat 30.415433, - :lon -87.54827499999999, - :house-number "13151", - :street "County Road 95", - :city "Elberta", - :state-abbrev "AL", - :zip "36530"} - {:lat 29.0252997, - :lon -98.50175449999999, - :house-number "300", - :street "Hillside", - :city "Pleasanton", - :state-abbrev "TX", - :zip "78064"} - {:lat 44.10125660000001, - :lon -96.5869936, - :house-number "22656", - :street "South Dakota 13", - :city "Flandreau", - :state-abbrev "SD", - :zip "57028"} - {:lat 47.6908723, - :lon -117.5036908, - :house-number "3201-4915", - :street "North Indian Bluff Road", - :city "Spokane", - :state-abbrev "WA", - :zip "99224"} - {:lat 32.3482789, - :lon -98.16811640000002, - :house-number "27485", - :street "North Sh108", - :city "Stephenville", - :state-abbrev "TX", - :zip "76401"} - {:lat 65.068141, - :lon -146.1060121, - :house-number "17030", - :street "Chena Hot Springs Road", - :city "Fairbanks", - :state-abbrev "AK", - :zip "99712"} - {:lat 46.807219, - :lon -68.121844, - :house-number "1747", - :street "Washburn Road", - :city "Washburn", - :state-abbrev "ME", - :zip "04786"} - {:lat 41.4077159, - :lon -91.79534540000002, - :house-number "1795", - :street "170th Street", - :city "Wellman", - :state-abbrev "IA", - :zip "52356"} - {:lat 29.9355215, - :lon -96.1070745, - :house-number "42560", - :street "Harpers Church Road", - :city "Bellville", - :state-abbrev "TX", - :zip "77418"} - {:lat 29.7829284, - :lon -97.2045977, - :house-number "6921", - :street "Three Mile Road", - :city "Flatonia", - :state-abbrev "TX", - :zip "78941"} - {:lat 37.299598, - :lon -113.101774, - :house-number "9", - :street "Kolob Terrace Road", - :city "Virgin", - :state-abbrev "UT", - :zip "84779"} - {:lat 36.190323, - :lon -96.37033199999999, - :house-number "2229", - :street "North Cocomo Loop", - :city "Mannford", - :state-abbrev "OK", - :zip "74044"} - {:lat 44.9343023, - :lon -71.68654409999999, - :house-number "185-1883", - :street "Sable Mountain Road", - :city "Canaan", - :state-abbrev "VT", - :zip "05903"} - {:lat 33.9722673, - :lon -91.6113769, - :house-number "15729", - :street "Arkansas 114", - :city "Gould", - :state-abbrev "AR", - :zip "71643"} - {:lat 40.1208677, - :lon -78.13654430000001, - :house-number "2139", - :street "Schenck Road", - :city "Wells Tannery", - :state-abbrev "PA", - :zip "16691"} - {:lat 32.5836029, - :lon -85.344824, - :house-number "575", - :street "Lee Road 40", - :city "Opelika", - :state-abbrev "AL", - :zip "36804"} - {:lat 32.1291719, - :lon -98.48731199999999, - :house-number "770", - :street "County Road 462", - :city "De Leon", - :state-abbrev "TX", - :zip "76444"} - {:lat 39.5802385, - :lon -103.5189258, - :house-number "9794-10792", - :street "County Road 1", - :city "Genoa", - :state-abbrev "CO", - :zip "80818"} - {:lat 37.46576719999999, - :lon -103.4180605, - :house-number "32523-32943", - :street "County Road 193.5", - :city "Kim", - :state-abbrev "CO", - :zip "81049"} - {:lat 43.5907349, - :lon -100.0809238, - :house-number "26279", - :street "304th Avenue", - :city "Winner", - :state-abbrev "SD", - :zip "57580"} - {:lat 40.7147525, - :lon -85.39709859999999, - :house-number "2748", - :street "East 800 South", - :city "Warren", - :state-abbrev "IN", - :zip "46792"} - {:lat 35.6193693, - :lon -80.34309979999999, - :house-number "1550", - :street "Poole Road", - :city "Salisbury", - :state-abbrev "NC", - :zip "28146"} - {:lat 41.5718425, - :lon -74.151854, - :house-number "334-336", - :street "Lake Osiris Road", - :city "Walden", - :state-abbrev "NY", - :zip "12586"} - {:lat 43.314036, - :lon -96.83119699999999, - :house-number "46865", - :street "281st Street", - :city "Lennox", - :state-abbrev "SD", - :zip "57039"} - {:lat 42.841993, - :lon -92.182942, - :house-number "1444", - :street "Tahoe Avenue", - :city "Sumner", - :state-abbrev "IA", - :zip "50674"} - {:lat 36.4249024, - :lon -90.1034226, - :house-number "29846", - :street "County Road 305", - :city "Campbell", - :state-abbrev "MO", - :zip "63933"} - {:lat 42.2602933, - :lon -121.5564791, - :house-number "19047", - :street "Highway 140 East", - :city "Dairy", - :state-abbrev "OR", - :zip "97625"} - {:lat 35.145161, - :lon -113.511055, - :house-number "9161", - :street "North Concho Drive", - :city "Kingman", - :state-abbrev "AZ", - :zip "86401"} - {:lat 44.9144835, - :lon -69.1501892, - :house-number "256", - :street "Clark's Hill Road", - :city "Stetson", - :state-abbrev "ME", - :zip "04488"} - {:lat 46.133132, - :lon -112.668086, - :house-number "2627", - :street "Telegraph Gulch", - :city "Butte", - :state-abbrev "MT", - :zip "59701"} - {:lat 36.1289603, - :lon -87.55618969999999, - :house-number "1588", - :street "Tummins Road", - :city "McEwen", - :state-abbrev "TN", - :zip "37101"} - {:lat 40.4173527, - :lon -74.3017223, - :house-number "145-175", - :street "Jake Brown Road", - :city "Old Bridge Township", - :state-abbrev "NJ", - :zip "08857"} - {:lat 48.0951728, - :lon -101.3786815, - :house-number "6001", - :street "135th Avenue Southwest", - :city "Minot", - :state-abbrev "ND", - :zip "58701"} - {:lat 41.514026, - :lon -93.362155, - :house-number "402", - :street "Molly Court", - :city "Runnells", - :state-abbrev "IA", - :zip "50237"} - {:lat 38.358538, - :lon -87.102161, - :house-number "4410", - :street "South 3rd Street", - :city "Velpen", - :state-abbrev "IN", - :zip "47590"} - {:lat 30.6230283, - :lon -91.0465433, - :house-number "12512", - :street "Greenwell Spring Pt Hudso Road", - :city "Zachary", - :state-abbrev "LA", - :zip "70791"} - {:lat 40.7192098, - :lon -78.11898719999999, - :house-number "1446", - :street "Centre Line Road", - :city "Warriors Mark", - :state-abbrev "PA", - :zip "16877"} - {:lat 39.3107383, - :lon -122.5328865, - :house-number "4475", - :street "Lodoga Stonyford Road", - :city "Stonyford", - :state-abbrev "CA", - :zip "95979"} - {:lat 43.9247077, - :lon -69.8961209, - :house-number "261", - :street "Old Bath Road", - :city "Brunswick", - :state-abbrev "ME", - :zip "04011"} - {:lat 38.2939375, - :lon -95.76334899999999, - :house-number "1825", - :street "Kafir Road", - :city "Burlington", - :state-abbrev "KS", - :zip "66839"} - {:lat 38.7039277, - :lon -93.25010549999999, - :house-number "901", - :street "South Limit Avenue", - :city "Sedalia", - :state-abbrev "MO", - :zip "65301"} - {:lat 31.6810774, - :lon -92.2265779, - :house-number "118", - :street "Belah Cemetery Road", - :city "Trout", - :state-abbrev "LA", - :zip "71371"} - {:lat 39.169834, - :lon -83.60190999999999, - :house-number "5794", - :street "South R 247", - :city "Hillsboro", - :state-abbrev "OH", - :zip "45133"} - {:lat 61.99231690000001, - :lon -146.7686644, - :house-number "2446", - :street "Glenn Highway", - :city "Glennallen", - :state-abbrev "AK", - :zip "99588"} - {:lat 38.3477305, - :lon -95.6188909, - :house-number "2200-2298", - :street "Shetland Road", - :city "Waverly", - :state-abbrev "KS", - :zip "66871"} - {:lat 36.9082415, - :lon -76.90512439999999, - :house-number "8332", - :street "Bell Avenue", - :city "Ivor", - :state-abbrev "VA", - :zip "23866"} - {:lat 41.1928259, - :lon -74.66955899999999, - :house-number "111", - :street "Haggerty Road", - :city "Wantage", - :state-abbrev "NJ", - :zip "07461"} - {:lat 32.7562537, - :lon -83.8201728, - :house-number "5700", - :street "Stokes Road", - :city "Lizella", - :state-abbrev "GA", - :zip "31052"} - {:lat 29.3102213, - :lon -95.74301249999999, - :house-number "19028-19030", - :street "Old Guy Road", - :city "Damon", - :state-abbrev "TX", - :zip "77430"} - {:lat 39.7652264, - :lon -93.3202542, - :house-number "28757", - :street "Bear Drive", - :city "Meadville", - :state-abbrev "MO", - :zip "64659"} - {:lat 39.2299951, - :lon -87.61143679999999, - :house-number "21000-22464", - :street "County Road 550 North", - :city "West Union", - :state-abbrev "IL", - :zip "62477"} - {:lat 39.2699827, - :lon -77.43403359999999, - :house-number "1619-1631", - :street "Park Mills Road", - :city "Adamstown", - :state-abbrev "MD", - :zip "21710"} - {:lat 33.9215182, - :lon -80.05810799999999, - :house-number "8080", - :street "Forge Road", - :city "Turbeville", - :state-abbrev "SC", - :zip "29162"} - {:lat 34.6621039, - :lon -102.139477, - :house-number "650-698", - :street "County Road 523", - :city "Happy", - :state-abbrev "TX", - :zip "79042"} - {:lat 31.809885, - :lon -86.4015411, - :house-number "1183", - :street "Center Ridge Road", - :city "Luverne", - :state-abbrev "AL", - :zip "36049"} - {:lat 41.3964881, - :lon -84.4180001, - :house-number "3001-3941", - :street "Wieland Road", - :city "Defiance", - :state-abbrev "OH", - :zip "43512"} - {:lat 29.7936609, - :lon -93.18750399999999, - :house-number "632", - :street "Wakefield Road", - :city "Cameron", - :state-abbrev "LA", - :zip "70631"} - {:lat 41.272848, - :lon -104.617244, - :house-number "2080", - :street "Road 136", - :city "Cheyenne", - :state-abbrev "WY", - :zip "82009"} - {:lat 32.645886, - :lon -87.5942093, - :house-number "12771", - :street "Al Highway 25", - :city "Greensboro", - :state-abbrev "AL", - :zip "36744"} - {:lat 31.804467, - :lon -98.63238, - :house-number "2000", - :street "Ranch Road 573", - :city "Comanche", - :state-abbrev "TX", - :zip "76442"} - {:lat 41.14615440000001, - :lon -92.5845738, - :house-number "22087", - :street "Columbia Road", - :city "Eddyville", - :state-abbrev "IA", - :zip "52553"} - {:lat 39.3056362, - :lon -83.0713213, - :house-number "1329-2599", - :street "Alum Cliff Road", - :city "Chillicothe", - :state-abbrev "OH", - :zip "45601"} - {:lat 37.271048, - :lon -92.973175, - :house-number "1482", - :street "Ridge Road", - :city "Marshfield", - :state-abbrev "MO", - :zip "65706"} - {:lat 42.082152, - :lon -78.66308099999999, - :house-number "648", - :street "Parkside Drive", - :city "Limestone", - :state-abbrev "NY", - :zip "14753"} - {:lat 44.5315223, - :lon -106.2157111, - :house-number "2799", - :street "Tipperary Road", - :city "Arvada", - :state-abbrev "WY", - :zip "82831"} - {:lat 46.4295937, - :lon -101.6249409, - :house-number "5746-5768", - :street "68th Street Southwest", - :city "Carson", - :state-abbrev "ND", - :zip "58529"} - {:lat 36.5835077, - :lon -92.247591, - :house-number "220", - :street "Bluetick Ridge Lane", - :city "Tecumseh", - :state-abbrev "MO", - :zip "65760"} - {:lat 38.1613425, - :lon -91.60582130000002, - :house-number "1183", - :street "State Highway U", - :city "Bland", - :state-abbrev "MO", - :zip "65014"} - {:lat 37.7563672, - :lon -95.098474, - :house-number "151", - :street "4800th Street", - :city "Savonburg", - :state-abbrev "KS", - :zip "66772"} - {:lat 36.8673735, - :lon -90.289456, - :house-number "859", - :street "County Road 572", - :city "Poplar Bluff", - :state-abbrev "MO", - :zip "63901"} - {:lat 40.26783959999999, - :lon -80.86239540000001, - :house-number "4225-4433", - :street "Smithfield-Adena Road", - :city "Adena", - :state-abbrev "OH", - :zip "43901"} - {:lat 36.4587022, - :lon -93.9533701, - :house-number "19057", - :street "U.S. Highway 62", - :city "Garfield", - :state-abbrev "AR", - :zip "72732"} - {:lat 41.7480865, - :lon -89.4574213, - :house-number "1101-1185", - :street "Illinois 26", - :city "Dixon", - :state-abbrev "IL", - :zip "61021"} - {:lat 41.8207449, - :lon -83.91851009999999, - :house-number "6949", - :street "Crockett Highway", - :city "Blissfield", - :state-abbrev "MI", - :zip "49228"} - {:lat 36.5258829, - :lon -105.4877284, - :house-number "281", - :street "Cuchilla Road", - :city "Taos", - :state-abbrev "NM", - :zip "87571"} - {:lat 44.9585579, - :lon -73.69073999999999, - :house-number "507", - :street "Lamberton Road", - :city "Mooers Forks", - :state-abbrev "NY", - :zip "12959"} - {:lat 35.4931658, - :lon -95.1962375, - :house-number "936", - :street "U.S. Highway 64", - :city "Webbers Falls", - :state-abbrev "OK", - :zip "74470"} - {:lat 62.599373, - :lon -150.2273, - :house-number "4142", - :street "North Parks Highway", - :city "Talkeetna", - :state-abbrev "AK", - :zip "99676"} - {:lat 25.775827, - :lon -80.2161845, - :house-number "219", - :street "Northwest 13th Avenue", - :city "Miami", - :state-abbrev "FL", - :zip "33125"} - {:lat 63.55269000000001, - :lon -145.866618, - :house-number "1221", - :street "Richardson Highway", - :city "Delta Junction", - :state-abbrev "AK", - :zip "99737"} - {:lat 32.5791956, - :lon -102.6688225, - :house-number "1068", - :street "County Road 305", - :city "Seminole", - :state-abbrev "TX", - :zip "79360"} - {:lat 33.943168, - :lon -78.9874791, - :house-number "6000-6244", - :street "Hucks Road", - :city "Conway", - :state-abbrev "SC", - :zip "29526"} - {:lat 41.9291494, - :lon -122.4036239, - :house-number "11100", - :street "Heather Lane", - :city "Hornbrook", - :state-abbrev "CA", - :zip "96044"} - {:lat 31.3419491, - :lon -88.0256923, - :house-number "435", - :street "Toinette Road", - :city "Wagarville", - :state-abbrev "AL", - :zip "36585"} - {:lat 44.954284, - :lon -116.875041, - :house-number "54253", - :street "Oregon 86", - :city "Halfway", - :state-abbrev "OR", - :zip "97834"} - {:lat 41.583846, - :lon -80.54728399999999, - :house-number "6123", - :street "Pymatuning Lake Road", - :city "Andover", - :state-abbrev "OH", - :zip "44003"} - {:lat 45.9565305, - :lon -114.1214446, - :house-number "100-198", - :street "Lazy Pine Road", - :city "Darby", - :state-abbrev "MT", - :zip "59829"} - {:lat 35.150464, - :lon -89.92967600000001, - :house-number "3887", - :street "Faxon Avenue", - :city "Memphis", - :state-abbrev "TN", - :zip "38122"} - {:lat 41.3616879, - :lon -96.9324879, - :house-number "2351", - :street "43 Road", - :city "Linwood", - :state-abbrev "NE", - :zip "68036"} - {:lat 48.8364755, - :lon -96.1627155, - :house-number "30000-30236", - :street "210th Avenue", - :city "Greenbush", - :state-abbrev "MN", - :zip "56726"} - {:lat 44.3398472, - :lon -97.4646326, - :house-number "43701-43745", - :street "210th Street", - :city "De Smet", - :state-abbrev "SD", - :zip "57231"} - {:lat 47.97314060000001, - :lon -110.1031342, - :house-number "3091", - :street "White Rocks Road", - :city "Big Sandy", - :state-abbrev "MT", - :zip "59520"} - {:lat 32.7834189, - :lon -116.8897473, - :house-number "804-880", - :street "Willow Glen Drive", - :city "El Cajon", - :state-abbrev "CA", - :zip "92019"} - {:lat 29.329903, - :lon -98.287882, - :house-number "9417", - :street "Stuart Road", - :city "San Antonio", - :state-abbrev "TX", - :zip "78263"} - {:lat 41.0161228, - :lon -94.4307601, - :house-number "1990", - :street "Clover Avenue", - :city "Creston", - :state-abbrev "IA", - :zip "50801"} - {:lat 43.3737186, - :lon -98.3097361, - :house-number "27600-27698", - :street "394th Avenue", - :city "Armour", - :state-abbrev "SD", - :zip "57313"} - {:lat 33.9705571, - :lon -88.6153678, - :house-number "30001-30019", - :street "Quail Cove", - :city "Okolona", - :state-abbrev "MS", - :zip "38860"} - {:lat 32.9355839, - :lon -92.8595831, - :house-number "290", - :street "Ella Ford Road", - :city "Haynesville", - :state-abbrev "LA", - :zip "71038"} - {:lat 37.9335619, - :lon -78.3050973, - :house-number "399", - :street "Two Rivers Drive", - :city "Troy", - :state-abbrev "VA", - :zip "22974"} - {:lat 47.4686878, - :lon -105.4528496, - :house-number "467-605", - :street "Montana 200", - :city "Circle", - :state-abbrev "MT", - :zip "59215"} - {:lat 44.1133821, - :lon -93.1815084, - :house-number "2697", - :street "Kenyon Road", - :city "Owatonna", - :state-abbrev "MN", - :zip "55060"} - {:lat 44.287157, - :lon -76.142338, - :house-number "42662", - :street "Thurso Bay", - :city "Clayton", - :state-abbrev "NY", - :zip "13624"} - {:lat 41.0662326, - :lon -104.1784873, - :house-number "400-496", - :street "County Road 158", - :city "Carpenter", - :state-abbrev "WY", - :zip "82054"} - {:lat 40.3243005, - :lon -93.0029117, - :house-number "14470", - :street "East Gate Road", - :city "Green City", - :state-abbrev "MO", - :zip "63545"} - {:lat 42.7346338, - :lon -75.5133221, - :house-number "198-422", - :street "Castle Hill Road", - :city "Sherburne", - :state-abbrev "NY", - :zip "13460"} - {:lat 34.337856, - :lon -82.322595, - :house-number "219", - :street "Timms Road", - :city "Donalds", - :state-abbrev "SC", - :zip "29638"} - {:lat 43.6759365, - :lon -88.2080613, - :house-number "W1722", - :street "County Road B", - :city "Eden", - :state-abbrev "WI", - :zip "53019"} - {:lat 38.8787613, - :lon -91.8143237, - :house-number "8290", - :street "County Road 134", - :city "Fulton", - :state-abbrev "MO", - :zip "65251"} - {:lat 32.3607249, - :lon -97.95839389999999, - :house-number "2400", - :street "Rock Church Highway", - :city "Tolar", - :state-abbrev "TX", - :zip "76476"} - {:lat 38.51624109999999, - :lon -94.0457396, - :house-number "1585-1615", - :street "Northwest 1050 Road", - :city "Urich", - :state-abbrev "MO", - :zip "64788"} - {:lat 41.97421019999999, - :lon -100.5062283, - :house-number "83820", - :street "Gaston Road", - :city "Thedford", - :state-abbrev "NE", - :zip "69166"} - {:lat 41.6188953, - :lon -112.3642282, - :house-number "13469-14379", - :street "Utah 102", - :city "Tremonton", - :state-abbrev "UT", - :zip "84337"} - {:lat 46.5532026, - :lon -106.0391607, - :house-number "1370", - :street "Montana 59", - :city "Miles City", - :state-abbrev "MT", - :zip "59301"} - {:lat 43.641095, - :lon -111.048747, - :house-number "2699", - :street "East 5000 South", - :city "Victor", - :state-abbrev "ID", - :zip "83455"} - {:lat 29.4590646, - :lon -99.1169827, - :house-number "3883", - :street "Texas 173", - :city "Hondo", - :state-abbrev "TX", - :zip "78861"} - {:lat 33.3409668, - :lon -102.4427318, - :house-number "932", - :street "County Road 230", - :city "Meadow", - :state-abbrev "TX", - :zip "79345"} - {:lat 30.0251478, - :lon -91.00133609999999, - :house-number "7035", - :street "Louisiana 70", - :city "Belle Rose", - :state-abbrev "LA", - :zip "70341"} - {:lat 34.9966929, - :lon -81.89568729999999, - :house-number "295", - :street "Mule Farm Road", - :city "Spartanburg", - :state-abbrev "SC", - :zip "29303"} - {:lat 37.0135164, - :lon -106.9052055, - :house-number "7390F", - :street "County Road 359", - :city "Pagosa Springs", - :state-abbrev "CO", - :zip "81147"} - {:lat 34.3648639, - :lon -80.7861429, - :house-number "814", - :street "Rolling Hills Road", - :city "Ridgeway", - :state-abbrev "SC", - :zip "29130"} - {:lat 35.5054713, - :lon -80.67574979999999, - :house-number "6551-6651", - :street "Miller Road", - :city "Kannapolis", - :state-abbrev "NC", - :zip "28081"} - {:lat 32.3409754, - :lon -91.7168365, - :house-number "275", - :street "Highway 584", - :city "Rayville", - :state-abbrev "LA", - :zip "71269"} - {:lat 33.0372336, - :lon -115.5756934, - :house-number "401-499", - :street "Boarts Road", - :city "Brawley", - :state-abbrev "CA", - :zip "92227"} - {:lat 30.8796596, - :lon -84.6495801, - :house-number "944", - :street "John Sam Road", - :city "Bainbridge", - :state-abbrev "GA", - :zip "39817"} - {:lat 35.7023803, - :lon -119.5085531, - :house-number "2479", - :street "Gun Club Road", - :city "Wasco", - :state-abbrev "CA", - :zip "93280"} - {:lat 32.1138939, - :lon -94.367318, - :house-number "382", - :street "County Road 302", - :city "Carthage", - :state-abbrev "TX", - :zip "75633"} - {:lat 40.7513715, - :lon -85.4486616, - :house-number "5112-5510", - :street "South Warren Road", - :city "Warren", - :state-abbrev "IN", - :zip "46792"} - {:lat 43.5870284, - :lon -122.0120442, - :house-number "26150", - :street "Oregon 58", - :city "Crescent", - :state-abbrev "OR", - :zip "97733"} - {:lat 30.1014042, - :lon -96.0686004, - :house-number "145", - :street "Calvit Street", - :city "Hempstead", - :state-abbrev "TX", - :zip "77445"} - {:lat 39.9977088, - :lon -91.7154652, - :house-number "27525", - :street "State Highway N", - :city "Ewing", - :state-abbrev "MO", - :zip "63440"} - {:lat 43.9067968, - :lon -92.8634064, - :house-number "18756-18998", - :street "720th Street", - :city "Hayfield", - :state-abbrev "MN", - :zip "55940"} - {:lat 38.319152, - :lon -75.838825, - :house-number "22358", - :street "Deep Branch Road", - :city "Quantico", - :state-abbrev "MD", - :zip "21856"} - {:lat 39.7386659, - :lon -75.0294809, - :house-number "346", - :street "Johnson Road", - :city "Sicklerville", - :state-abbrev "NJ", - :zip "08081"} - {:lat 41.9543142, - :lon -76.7888085, - :house-number "260", - :street "Monkey Run Road", - :city "Gillett", - :state-abbrev "PA", - :zip "16925"} - {:lat 41.1009, - :lon -86.6770219, - :house-number "3828", - :street "West 300 North", - :city "Winamac", - :state-abbrev "IN", - :zip "46996"} - {:lat 44.011282, - :lon -89.2391004, - :house-number "975", - :street "Cypress Road", - :city "Neshkoro", - :state-abbrev "WI", - :zip "54960"} - {:lat 47.0958654, - :lon -99.3796684, - :house-number "5252-5298", - :street "22nd Street Southeast", - :city "Woodworth", - :state-abbrev "ND", - :zip "58496"} - {:lat 32.4864678, - :lon -100.6358039, - :house-number "579-1053", - :street "County Road 169", - :city "Roscoe", - :state-abbrev "TX", - :zip "79545"} - {:lat 46.0631211, - :lon -93.41270970000001, - :house-number "3213", - :street "Falcon Street", - :city "Isle", - :state-abbrev "MN", - :zip "56342"} - {:lat 44.6275708, - :lon -73.5379217, - :house-number "35", - :street "Stone Bridge Way", - :city "Plattsburgh", - :state-abbrev "NY", - :zip "12901"} - {:lat 40.4012142, - :lon -80.763559, - :house-number "1010-1311", - :street "Township Road 223", - :city "Richmond", - :state-abbrev "OH", - :zip "43944"} - {:lat 37.701113, - :lon -77.42609999999999, - :house-number "9479", - :street "Sliding Hill Road", - :city "Ashland", - :state-abbrev "VA", - :zip "23005"} - {:lat 27.0896397, - :lon -80.5149292, - :house-number "9601", - :street "T M Road", - :city "Indiantown", - :state-abbrev "FL", - :zip "34956"} - {:lat 30.2316212, - :lon -96.2623417, - :house-number "10402", - :street "FM 2193", - :city "Brenham", - :state-abbrev "TX", - :zip "77833"} - {:lat 39.9809, - :lon -91.35653490000001, - :house-number "4134", - :street "North 36th Street", - :city "Quincy", - :state-abbrev "IL", - :zip "62305"} - {:lat 27.780107, - :lon -97.91449, - :house-number "1903", - :street "2nd Street", - :city "Agua Dulce", - :state-abbrev "TX", - :zip "78330"} - {:lat 36.0824814, - :lon -94.78128149999999, - :house-number "463210", - :street "East 594 Road", - :city "Kansas", - :state-abbrev "OK", - :zip "74347"} - {:lat 48.889109, - :lon -106.46857, - :house-number "247", - :street "Roanwood Road", - :city "Opheim", - :state-abbrev "MT", - :zip "59250"} - {:lat 34.2245448, - :lon -79.9830906, - :house-number "1720", - :street "Country Manor Road", - :city "Timmonsville", - :state-abbrev "SC", - :zip "29161"} - {:lat 44.9538531, - :lon -89.07141879999999, - :house-number "16480", - :street "Hemlock Road", - :city "Birnamwood", - :state-abbrev "WI", - :zip "54414"} - {:lat 33.0803727, - :lon -108.4894079, - :house-number "535", - :street "Turkey Creek Road", - :city "Silver City", - :state-abbrev "NM", - :zip "88061"} - {:lat 45.780511, - :lon -110.115136, - :house-number "398", - :street "North Yellowstone Trail Road", - :city "Big Timber", - :state-abbrev "MT", - :zip "59011"} - {:lat 31.565726, - :lon -97.78056699999999, - :house-number "820", - :street "Cr 239", - :city "Gatesville", - :state-abbrev "TX", - :zip "76528"} - {:lat 37.5001623, - :lon -77.654427, - :house-number "112-208", - :street "Coalfield Road", - :city "Midlothian", - :state-abbrev "VA", - :zip "23114"} - {:lat 33.4192279, - :lon -105.416806, - :house-number "27587", - :street "U.S. 70", - :city "Glencoe", - :state-abbrev "NM", - :zip "88324"} - {:lat 57.0565648, - :lon -135.3704941, - :house-number "1190", - :street "Seward Avenue", - :city "Sitka", - :state-abbrev "AK", - :zip "99835"} - {:lat 37.6143574, - :lon -114.4812157, - :house-number "600", - :street "Clover Creek Road", - :city "Caliente", - :state-abbrev "NV", - :zip "89008"} - {:lat 42.669734, - :lon -82.57851099999999, - :house-number "7882", - :street "Morrow Road", - :city "Marine City", - :state-abbrev "MI", - :zip "48039"} - {:lat 43.1411556, - :lon -94.1562886, - :house-number "1514-1568", - :street "260th Street", - :city "Algona", - :state-abbrev "IA", - :zip "50511"} - {:lat 35.4250248, - :lon -81.5005142, - :house-number "4632", - :street "Fallston Road", - :city "Shelby", - :state-abbrev "NC", - :zip "28150"} - {:lat 31.2472719, - :lon -97.5642558, - :house-number "5690", - :street "Farm to Market 184", - :city "Gatesville", - :state-abbrev "TX", - :zip "76528"} - {:lat 41.54929, - :lon -73.2168571, - :house-number "98-108", - :street "Judson Avenue", - :city "Woodbury", - :state-abbrev "CT", - :zip "06798"} - {:lat 35.225912, - :lon -84.789648, - :house-number "637", - :street "Dry Valley Road Northeast", - :city "Cleveland", - :state-abbrev "TN", - :zip "37312"} - {:lat 48.91047709999999, - :lon -104.1266629, - :house-number "201-363", - :street "McElroy Road", - :city "Westby", - :state-abbrev "MT", - :zip "59275"} - {:lat 38.5357658, - :lon -78.74227309999999, - :house-number "1358", - :street "Warner Lane", - :city "Harrisonburg", - :state-abbrev "VA", - :zip "22802"} - {:lat 31.7556515, - :lon -84.3729352, - :house-number "1605-1611", - :street "Sellers Road", - :city "Dawson", - :state-abbrev "GA", - :zip "39842"} - {:lat 34.6606528, - :lon -78.6013504, - :house-number "852", - :street "Smith and Hair Road", - :city "Elizabethtown", - :state-abbrev "NC", - :zip "28337"} - {:lat 35.4922289, - :lon -86.82591479999999, - :house-number "1797-1927", - :street "New Columbia Highway", - :city "Lewisburg", - :state-abbrev "TN", - :zip "37091"} - {:lat 35.819588, - :lon -80.92777199999999, - :house-number "2097", - :street "Old Wilkesboro Road", - :city "Statesville", - :state-abbrev "NC", - :zip "28625"} - {:lat 42.826871, - :lon -76.36948699999999, - :house-number "5625", - :street "Mack Road", - :city "Skaneateles", - :state-abbrev "NY", - :zip "13152"} - {:lat 34.36168, - :lon -88.8239395, - :house-number "100-198", - :street "Road 1", - :city "Tupelo", - :state-abbrev "MS", - :zip "38804"} - {:lat 37.685311, - :lon -106.6649902, - :house-number "1077-1487", - :street "Colorado 149", - :city "South Fork", - :state-abbrev "CO", - :zip "81154"} - {:lat 32.54768, - :lon -82.0425238, - :house-number "41-51", - :street "Georgia 121", - :city "Twin City", - :state-abbrev "GA", - :zip "30471"} - {:lat 38.7840047, - :lon -97.15234319999999, - :house-number "1283-1293", - :street "1300 Avenue", - :city "Abilene", - :state-abbrev "KS", - :zip "67410"} - {:lat 37.285231, - :lon -93.7755416, - :house-number "363", - :street "State Highway WW", - :city "Miller", - :state-abbrev "MO", - :zip "65707"} - {:lat 34.0449385, - :lon -82.6381945, - :house-number "1310", - :street "Bobby Brown State Park Road", - :city "Elberton", - :state-abbrev "GA", - :zip "30635"} - {:lat 41.29878480000001, - :lon -123.220365, - :house-number "27529-27827", - :street "Sawyers Bar Road", - :city "Etna", - :state-abbrev "CA", - :zip "96027"} - {:lat 41.44222329999999, - :lon -75.8678892, - :house-number "1515", - :street "Keelersburg Road", - :city "Tunkhannock", - :state-abbrev "PA", - :zip "18657"} - {:lat 36.60397409999999, - :lon -80.0159182, - :house-number "945", - :street "Old Well Road", - :city "Spencer", - :state-abbrev "VA", - :zip "24165"} - {:lat 35.5220545, - :lon -98.5358859, - :house-number "3006", - :street "County Street 2500", - :city "Hydro", - :state-abbrev "OK", - :zip "73048"} - {:lat 47.1815654, - :lon -114.93413, - :house-number "16-62", - :street "Thompson Creek Road", - :city "Superior", - :state-abbrev "MT", - :zip "59872"} - {:lat 44.2569619, - :lon -117.0542315, - :house-number "729", - :street "Jonathan Road", - :city "Weiser", - :state-abbrev "ID", - :zip "83672"} - {:lat 29.8632357, - :lon -96.86068159999999, - :house-number "1900-2012", - :street "George Road", - :city "La Grange", - :state-abbrev "TX", - :zip "78945"} - {:lat 43.7119068, - :lon -102.1301612, - :house-number "19651", - :street "South Dakota 44", - :city "Interior", - :state-abbrev "SD", - :zip "57750"} - {:lat 39.277648, - :lon -106.962145, - :house-number "4305", - :street "Snowmass Creek Road", - :city "Snowmass", - :state-abbrev "CO", - :zip "81654"} - {:lat 34.7461054, - :lon -102.4947431, - :house-number "3240", - :street "U.S. 60", - :city "Hereford", - :state-abbrev "TX", - :zip "79045"} - {:lat 36.290432, - :lon -83.817886, - :house-number "1878", - :street "Hickory Valley Road", - :city "Maynardville", - :state-abbrev "TN", - :zip "37807"} - {:lat 44.9100772, - :lon -94.6225424, - :house-number "11258", - :street "570th Avenue", - :city "Cosmos", - :state-abbrev "MN", - :zip "56228"} - {:lat 35.8832669, - :lon -90.98008, - :house-number "8399", - :street "CR 194", - :city "Cash", - :state-abbrev "AR", - :zip "72421"} - {:lat 42.5763337, - :lon -101.1235419, - :house-number "18-30", - :street "302nd Avenue", - :city "Valentine", - :state-abbrev "NE", - :zip "69201"} - {:lat 32.3376667, - :lon -80.528605, - :house-number "1", - :street "Whitams Island", - :city "Saint Helena Island", - :state-abbrev "SC", - :zip "29920"} - {:lat 38.2782884, - :lon -105.6437323, - :house-number "710", - :street "Lake Creek Lane", - :city "Cotopaxi", - :state-abbrev "CO", - :zip "81223"} - {:lat 47.605072, - :lon -92.3040578, - :house-number "6500-6698", - :street "Giants Ridge Road", - :city "Embarrass", - :state-abbrev "MN", - :zip "55732"} - {:lat 32.6444029, - :lon -95.56368700000002, - :house-number "25678", - :street "County Road 457", - :city "Mineola", - :state-abbrev "TX", - :zip "75773"} - {:lat 43.244654, - :lon -83.739317, - :house-number "10787", - :street "Rose Lane", - :city "Birch Run", - :state-abbrev "MI", - :zip "48415"} - {:lat 38.3867667, - :lon -78.0820758, - :house-number "23123-23159", - :street "Roland Road", - :city "Rapidan", - :state-abbrev "VA", - :zip "22733"} - {:lat 39.062332, - :lon -123.243362, - :house-number "6091", - :street "Boonville Road", - :city "Ukiah", - :state-abbrev "CA", - :zip "95482"} - {:lat 42.7312246, - :lon -76.7093886, - :house-number "18", - :street "Sunset Beach Road", - :city "Aurora", - :state-abbrev "NY", - :zip "13026"} - {:lat 42.1696132, - :lon -118.5709799, - :house-number "44289", - :street "Whitehorse Ranch Lane", - :city "Fields", - :state-abbrev "OR", - :zip "97710"} - {:lat 34.61839, - :lon -92.8443247, - :house-number "24141", - :street "Old Hot Springs Highway", - :city "Hot Springs Village", - :state-abbrev "AR", - :zip "71909"} - {:lat 41.2517668, - :lon -104.2248914, - :house-number "6255", - :street "County Road 213", - :city "Pine Bluffs", - :state-abbrev "WY", - :zip "82082"} - {:lat 33.485464, - :lon -82.0220871, - :house-number "501", - :street "Delano Street", - :city "Augusta", - :state-abbrev "GA", - :zip "30904"} - {:lat 39.9526071, - :lon -90.9820034, - :house-number "2701-2799", - :street "North 1353rd Lane", - :city "Clayton", - :state-abbrev "IL", - :zip "62324"} - {:lat 43.0633923, - :lon -105.565439, - :house-number "28", - :street "Highland Loop Road", - :city "Douglas", - :state-abbrev "WY", - :zip "82633"} - {:lat 47.1533778, - :lon -99.0071076, - :house-number "7001-7089", - :street "18th Street Southeast", - :city "Pingree", - :state-abbrev "ND", - :zip "58476"} - {:lat 30.557063, - :lon -96.30854199999999, - :house-number "13546", - :street "Alacia Court", - :city "College Station", - :state-abbrev "TX", - :zip "77845"} - {:lat 40.0936749, - :lon -86.92878619999999, - :house-number "1435", - :street "West 400 North", - :city "Crawfordsville", - :state-abbrev "IN", - :zip "47933"} - {:lat 40.429191, - :lon -80.038403, - :house-number "666", - :street "Hestor Drive", - :city "Pittsburgh", - :state-abbrev "PA", - :zip "15220"} - {:lat 48.306407, - :lon -120.0517531, - :house-number "1", - :street "Benson Creek Drive", - :city "Twisp", - :state-abbrev "WA", - :zip "98856"} - {:lat 33.6578638, - :lon -96.4607095, - :house-number "435", - :street "Craft Road", - :city "Bells", - :state-abbrev "TX", - :zip "75414"} - {:lat 42.2964941, - :lon -83.148913, - :house-number "12821", - :street "Dix", - :city "Dearborn", - :state-abbrev "MI", - :zip "48120"} - {:lat 35.24744099999999, - :lon -83.625271, - :house-number "254", - :street "Otter Creek Road", - :city "Topton", - :state-abbrev "NC", - :zip "28781"} - {:lat 45.5650285, - :lon -121.0089962, - :house-number "4757-5201", - :street "Emerson Loop Road", - :city "The Dalles", - :state-abbrev "OR", - :zip "97058"} - {:lat 42.1992991, - :lon -97.03739809999999, - :house-number "85346-85398", - :street "575 Avenue", - :city "Wayne", - :state-abbrev "NE", - :zip "68787"} - {:lat 46.15602, - :lon -89.907809, - :house-number "14077", - :street "East Circle Lily Road", - :city "Manitowish Waters", - :state-abbrev "WI", - :zip "54545"} - {:lat 44.98760499999999, - :lon -93.990574, - :house-number "11500", - :street "Ferman Avenue Southwest", - :city "Waverly", - :state-abbrev "MN", - :zip "55390"} - {:lat 38.563198, - :lon -94.270044, - :house-number "28909", - :street "South Kircher Road", - :city "Harrisonville", - :state-abbrev "MO", - :zip "64701"} - {:lat 32.5621624, - :lon -99.7002526, - :house-number "822", - :street "Comanche Trail", - :city "Abilene", - :state-abbrev "TX", - :zip "79601"} - {:lat 41.2800528, - :lon -73.8347881, - :house-number "1510-1580", - :street "Whitehill Road", - :city "Yorktown Heights", - :state-abbrev "NY", - :zip "10598"} - {:lat 40.593251, - :lon -80.84441, - :house-number "628", - :street "Ohio 164", - :city "Salineville", - :state-abbrev "OH", - :zip "43945"} - {:lat 33.0242556, - :lon -81.9565897, - :house-number "501-899", - :street "Idlewood Road", - :city "Waynesboro", - :state-abbrev "GA", - :zip "30830"} - {:lat 34.3102816, - :lon -80.251449, - :house-number "681", - :street "Newsome Road", - :city "Bishopville", - :state-abbrev "SC", - :zip "29010"} - {:lat 39.332676, - :lon -78.240403, - :house-number "2616", - :street "Siler Road", - :city "Winchester", - :state-abbrev "VA", - :zip "22603"} - {:lat 45.96328279999999, - :lon -118.3866197, - :house-number "84325", - :street "Oregon 11", - :city "Milton-Freewater", - :state-abbrev "OR", - :zip "97862"} - {:lat 45.0264787, - :lon -69.599564, - :house-number "24", - :street "Cross Road", - :city "Wellington", - :state-abbrev "ME", - :zip "04942"} - {:lat 41.3111215, - :lon -85.8015207, - :house-number "2071-2199", - :street "East Riverside Drive", - :city "Warsaw", - :state-abbrev "IN", - :zip "46582"} - {:lat 37.4179811, - :lon -85.7573845, - :house-number "12280", - :street "North Jackson Highway", - :city "Magnolia", - :state-abbrev "KY", - :zip "42757"} - {:lat 42.484824, - :lon -72.8480639, - :house-number "1876", - :street "Spruce Corner Road", - :city "Ashfield", - :state-abbrev "MA", - :zip "01330"} - {:lat 35.2600134, - :lon -106.7031468, - :house-number "125-127", - :street "2nd Street Southeast", - :city "Rio Rancho", - :state-abbrev "NM", - :zip "87124"} - {:lat 37.155673, - :lon -122.024959, - :house-number "1000", - :street "Wilderfield Road", - :city "Los Gatos", - :state-abbrev "CA", - :zip "95033"} - {:lat 47.9367019, - :lon -91.4237666, - :house-number "3191", - :street "Fernberg Road", - :city "Ely", - :state-abbrev "MN", - :zip "55731"} - {:lat 30.114453, - :lon -97.9510375, - :house-number "8700", - :street "Farm to Market 967", - :city "Buda", - :state-abbrev "TX", - :zip "78610"} - {:lat 44.1517741, - :lon -98.0955537, - :house-number "22310", - :street "406th Avenue", - :city "Forestburg", - :state-abbrev "SD", - :zip "57314"} - {:lat 28.8448101, - :lon -99.0027399, - :house-number "5056-5638", - :street "Keystone Road", - :city "Pearsall", - :state-abbrev "TX", - :zip "78061"} - {:lat 32.918188, - :lon -84.89107200000001, - :house-number "493", - :street "Old Chipley Road", - :city "Pine Mountain", - :state-abbrev "GA", - :zip "31822"} - {:lat 35.872716, - :lon -78.63470640000001, - :house-number "409", - :street "Westbrook Drive", - :city "Raleigh", - :state-abbrev "NC", - :zip "27615"} - {:lat 35.9351531, - :lon -79.0572364, - :house-number "875-893", - :street "Estes Drive Extension", - :city "Chapel Hill", - :state-abbrev "NC", - :zip "27516"} - {:lat 34.8583854, - :lon -91.6012164, - :house-number "2993", - :street "Arkansas 249", - :city "Hazen", - :state-abbrev "AR", - :zip "72064"} - {:lat 41.1521072, - :lon -73.7005391, - :house-number "99", - :street "Pioneer Trail", - :city "Armonk", - :state-abbrev "NY", - :zip "10504"} - {:lat 66.5295218, - :lon -152.6634842, - :house-number "21", - :street "Tanana-Allakaket Winter Trail", - :city "Allakaket", - :state-abbrev "AK", - :zip "99720"} - {:lat 40.1315377, - :lon -89.09725139999999, - :house-number "2499", - :street "Bungtown Road", - :city "Kenney", - :state-abbrev "IL", - :zip "61749"} - {:lat 37.3424648, - :lon -86.01359289999999, - :house-number "770", - :street "Cave Hill Road", - :city "Munfordville", - :state-abbrev "KY", - :zip "42765"} - {:lat 39.2092304, - :lon -82.4539298, - :house-number "35887", - :street "State Route 324", - :city "Hamden", - :state-abbrev "OH", - :zip "45634"} - {:lat 36.456136, - :lon -76.87827, - :house-number "70", - :street "Tinkham Road", - :city "Eure", - :state-abbrev "NC", - :zip "27935"} - {:lat 48.3779906, - :lon -109.2142716, - :house-number "16475", - :street "Cleveland Road", - :city "Chinook", - :state-abbrev "MT", - :zip "59523"} - {:lat 37.6707712, - :lon -91.1372466, - :house-number "1465", - :street "Ponderosa", - :city "Bixby", - :state-abbrev "MO", - :zip "65439"} - {:lat 40.8572074, - :lon -98.3295741, - :house-number "1099", - :street "East Wildwood Drive", - :city "Grand Island", - :state-abbrev "NE", - :zip "68801"} - {:lat 41.9850861, - :lon -96.4640027, - :house-number "2160", - :street "U.S. 77", - :city "Lyons", - :state-abbrev "NE", - :zip "68038"} - {:lat 44.5762138, - :lon -98.681271, - :house-number "37500-37598", - :street "194th Street", - :city "Wessington", - :state-abbrev "SD", - :zip "57381"} - {:lat 32.2830071, - :lon -101.4354302, - :house-number "104", - :street "Broken Bow Road", - :city "Big Spring", - :state-abbrev "TX", - :zip "79720"} - {:lat 39.9008824, - :lon -102.6886584, - :house-number "22000-22998", - :street "County Road G", - :city "Yuma", - :state-abbrev "CO", - :zip "80759"} - {:lat 38.6578676, - :lon -75.1426649, - :house-number "23402", - :street "Camp Arrowhead Road", - :city "Lewes", - :state-abbrev "DE", - :zip "19958"} - {:lat 38.32789959999999, - :lon -113.0122461, - :house-number "186", - :street "4500 South", - :city "Milford", - :state-abbrev "UT", - :zip "84751"} - {:lat 47.726737, - :lon -117.19418, - :house-number "15708", - :street "East Lincoln Road", - :city "Spokane", - :state-abbrev "WA", - :zip "99217"} - {:lat 38.821582, - :lon -84.659021, - :house-number "14933", - :street "Walton-Verona Road", - :city "Verona", - :state-abbrev "KY", - :zip "41092"}) diff --git a/lein-commands/sample-dataset/metabase/sample_dataset/generate.clj b/lein-commands/sample-dataset/metabase/sample_dataset/generate.clj deleted file mode 100644 index 4c159674349..00000000000 --- a/lein-commands/sample-dataset/metabase/sample_dataset/generate.clj +++ /dev/null @@ -1,551 +0,0 @@ -(ns metabase.sample-dataset.generate - "Logic for generating the sample dataset. - Run this with `lein generate-sample-dataset`." - (:require [clojure - [edn :as edn] - [string :as str]] - [clojure.java - [io :as io] - [jdbc :as jdbc]] - [clojure.math.numeric-tower :as math] - [faker - [company :as company] - [internet :as internet] - [lorem :as lorem] - [name :as name]] - [jdistlib.core :as dist] - [medley.core :as m] - [metabase.db.spec :as dbspec] - [metabase.util :as u] - [metabase.util.date :as du]) - (:import java.util.Date)) - -(def ^:private ^:const sample-dataset-filename - (str (System/getProperty "user.dir") "/resources/sample-dataset.db")) - -(def ^:private num-rows-to-create - {:people 2500 - :products 200}) - -(def ^:private num-reviews-distribution - "Normal distribution sampled to determine number of reviews each product should have. Actual average number of - reviews will be slightly higher because negative values returned by the sample will be floored at 0 (e.g. a product - cannot have less than 0 reviews)." - (dist/normal 5 4)) - -(def ^:private num-orders-distibution - "Normal distribution sampled to determine number of orders each person should have." - (dist/normal 5 10)) - -;;; ## PEOPLE - -(defn ^Date random-date-between [^Date min, ^Date max] - (let [min-ms (.getTime min) - max-ms (.getTime max) - range (- max-ms min-ms) - d (Date.)] - (.setTime d (+ (long (rand range)) min-ms)) - d)) - -(def ^:private addresses (atom nil)) - -(defn- load-addresses! [] - (println "Loading addresses...") - (reset! addresses (edn/read-string (slurp "lein-commands/sample-dataset/metabase/sample_dataset/addresses.edn"))) - :ok) - -(defn- next-address [] - (when-not (seq @addresses) - (load-addresses!)) - (let [address (first @addresses)] - (swap! addresses rest) - address)) - -(defn- random-person [] - (let [first (name/first-name) - last (name/last-name) - addr (next-address)] - {:name (format "%s %s" first last) - :email (internet/free-email (format "%s.%s" first last)) - :password (str (java.util.UUID/randomUUID)) - :birth_date (random-date-between (du/relative-date :year -60) (du/relative-date :year -18)) - :address (str (:house-number addr) " " (:street addr)) - :city (:city addr) - :zip (:zip addr) - :state (:state-abbrev addr) - :latitude (:lat addr) - :longitude (:lon addr) - :source (rand-nth ["Google" "Twitter" "Facebook" "Organic" "Affiliate"]) - :created_at (random-date-between (du/relative-date :year -2) (du/relative-date :year 1))})) - -;;; ## PRODUCTS - -(defn- random-company-name [] - (first (company/names))) - -(defn- rejection-sample - "Sample from distribution `dist` until `pred` is truthy for the sampled value. - https://en.wikipedia.org/wiki/Rejection_sampling" - [pred dist] - (let [x (dist/sample dist)] - (if (pred x) - x - (rejection-sample pred dist)))) - -(defn- random-price [min max] - (let [range (- max min) - mean1 (+ min (* range 0.3)) - mean2 (+ min (* range 0.75)) - variance (/ range 8)] - ; Sample from a multi modal distribution (mix of two normal distributions - ; with means `mean1` and `mean2` and variance `variance`). - (rejection-sample #(<= min % max) (rand-nth [(dist/normal mean1 variance) - (dist/normal mean2 variance)])))) - -(def ^:private ^:const product-names - {:adjective '[Small, Ergonomic, Rustic, Intelligent, Gorgeous, Incredible, Fantastic, Practical, Sleek, Awesome, - Enormous, Mediocre, Synergistic, Heavy-Duty, Lightweight, Aerodynamic, Durable] - :material '[Steel, Wooden, Concrete, Plastic, Cotton, Granite, Rubber, Leather, Silk, Wool, Linen, Marble, Iron, - Bronze, Copper, Aluminum, Paper] - :product '[Chair, Car, Computer, Gloves, Pants, Shirt, Table, Shoes, Hat, Plate, Knife, Bottle, Coat, Lamp, - Keyboard, Bag, Bench, Clock, Watch, Wallet, Toucan]}) - -(defn- random-product-name [] - (format "%s %s %s" - (rand-nth (product-names :adjective)) - (rand-nth (product-names :material)) - (rand-nth (product-names :product)))) - -(def ^:private ean-checksum - (let [^:const weights (flatten (repeat 6 [1 3]))] - (fn [digits] - {:pre [(= 12 (count digits)) - (= 12 (count (apply str digits)))] - :post [(= 1 (count (str %)))]} - (as-> (reduce + (map (fn [digit weight] - (* digit weight)) - digits weights)) - it - (mod it 10) - (- 10 it) - (mod it 10))))) - -(defn- random-ean [] - {:post [(= (count %) 13)]} - (let [digits (vec (repeatedly 12 #(rand-int 10)))] - (->> (conj digits (ean-checksum digits)) - (apply str)))) - -(defn- random-product [] - {:ean (random-ean) - :title (random-product-name) - :category (rand-nth ["Widget" "Gizmo" "Gadget" "Doohickey"]) - :vendor (random-company-name) - :price (random-price 12 100) - :created_at (random-date-between (du/relative-date :year -2) (du/relative-date :year 1))}) - - -;;; ## ORDERS - -(def ^:private state->tax-rate - {"AK" 0.0 - "AL" 0.04 - "AR" 0.065 - "AZ" 0.056 - "CA" 0.075 - "CO" 0.029 - "CT" 0.0635 - "DC" 0.0575 - "DE" 0.0 - "FL" 0.06 - "GA" 0.04 - "HI" 0.04 - "IA" 0.06 - "ID" 0.06 - "IL" 0.0625 - "IN" 0.07 - "KS" 0.065 - "KY" 0.06 - "LA" 0.04 - "MA" 0.0625 - "MD" 0.06 - "ME" 0.055 - "MI" 0.06 - "MN" 0.06875 - "MO" 0.04225 - "MS" 0.07 - "MT" 0.0 - "NC" 0.0475 - "ND" 0.05 - "NE" 0.055 - "NH" 0.0 - "NJ" 0.07 - "NM" 0.05125 - "NV" 0.0685 - "NY" 0.04 - "OH" 0.0575 - "OK" 0.045 - "OR" 0.0 - "PA" 0.06 - "RI" 0.07 - "SC" 0.06 - "SD" 0.04 - "TN" 0.07 - "TX" 0.0625 - "UT" 0.047 - "VA" 0.043 - "VT" 0.06 - "WA" 0.065 - "WI" 0.05 - "WV" 0.06 - "WY" 0.04 - ;; Territories / Associated States / Armed Forces - just give these all zero - ;; These might come back from address/us-state-abbr - "AA" 0.0 ; Armed Forces - Americas - "AE" 0.0 ; Armed Forces - Europe - "AP" 0.0 ; Armed Forces - Pacific - "AS" 0.0 ; American Samoa - "FM" 0.0 ; Federated States of Micronesia - "GU" 0.0 ; Guam - "MH" 0.0 ; Marshall Islands - "MP" 0.0 ; Northern Mariana Islands - "PR" 0.0 ; Puerto Rico - "PW" 0.0 ; Palau - "VI" 0.0 ; Virgin Islands - }) - -(defn- max-date [& dates] - {:pre [(every? (partial instance? Date) dates)] - :post [(instance? Date %)]} - (doto (Date.) - (.setTime (apply max (map #(.getTime ^Date %) dates))))) - -(defn- min-date [& dates] - {:pre [(every? (partial instance? Date) dates)] - :post [(instance? Date %)]} - (doto (Date.) - (.setTime (apply min (map #(.getTime ^Date %) dates))))) - -(defn- with-probability - "Return `(f)` with probability `p`, else return nil." - [p f] - (when (> p (rand)) - (f))) - -(defn random-order [{:keys [state], :as ^Person person} {:keys [price], :as product}] - {:pre [(string? state) - (number? price)] - :post [(map? %)]} - (let [tax-rate (state->tax-rate state) - _ (assert tax-rate - (format "No tax rate found for state '%s'." state)) - created-at (random-date-between (min-date (:created_at person) - (:created_at product)) - (du/relative-date :year 2)) - price (if (> (.getTime created-at) (.getTime (Date. 118 0 1))) - (* 1.5 price) - price) - tax (u/round-to-decimals 2 (* price tax-rate))] - {:user_id (:id person) - :product_id (:id product) - :subtotal price - :tax tax - :quantity (random-price 1 5) - :discount (with-probability 0.1 #(random-price 0 10)) - :total (+ price tax) - :created_at created-at})) - - -;;; ## REVIEWS - -(defn random-review [product] - {:product_id (:id product) - :reviewer (internet/user-name) - :rating (rand-nth [1 1 - 2 2 2 - 3 3 - 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 - 5 5 5 5 5 5 5 5 5 5 5 5 5]) - :body (first (lorem/paragraphs)) - :created_at (random-date-between (:created_at product) (du/relative-date :year 2))}) - -(defn- add-ids [objs] - (map-indexed - (fn [id obj] - (assoc obj :id (inc id))) - objs)) - -(defn- create-randoms [n f] - (-> (take n (distinct (repeatedly f))) - add-ids)) - -(defn- product-add-reviews [product] - (let [num-reviews (max 0 (dist/sample num-reviews-distribution)) ; with 200 products should give us ~1000 reviews - reviews (vec (for [review (repeatedly num-reviews #(random-review product))] - (assoc review :product_id (:id product)))) - rating (if (seq reviews) (/ (reduce + (map :rating reviews)) - (count reviews)) - 0.0)] - (assoc product :reviews reviews, :rating (-> (* rating 10.0) - int - (/ 10.0))))) - -(defn- person-add-orders [products person] - {:pre [(sequential? products) - (map? person)] - :post [(map? %)]} - (let [num-orders (max 0 (dist/sample num-orders-distibution))] ; with 2500 people should give us ~15k orders - (if (zero? num-orders) - person - (assoc person :orders (vec (repeatedly num-orders #(random-order person (rand-nth products)))))))) - -(defn- add-autocorrelation - "Add autocorrelation with lag `lag` to field `k` by adding the value from `lag` steps back (and dividing by 2 to - retain roughly the same value range). https://en.wikipedia.org/wiki/Autocorrelation" - ([k xs] (add-autocorrelation 1 k xs)) - ([lag k xs] - (map (fn [prev next] - (update next k #(/ (+ % (k prev)) 2))) - xs - (drop lag xs)))) - -(defn- add-increasing-variance - "Gradually increase variance of field `k` by scaling it an (on average) increasingly larger random noise. - - https://en.wikipedia.org/wiki/Variance" - [k xs] - (let [n (count xs)] - (map-indexed (fn [i x] - ; Limit the noise to [0.1, 2.1]. - (update x k * (+ 1 (* (/ i n) (- (* 2 (rand)) 0.9))))) - xs))) - -(defn- add-seasonality - "Add seasonal component to field `k`. Seasonal variation (a multiplicative factor) is described with map - `seasonality-map` indexed into by `season-fn` (eg. month of year of field created_at)." - [season-fn k seasonality-map xs] - (for [x xs] - (update x k * (seasonality-map (season-fn x))))) - -(defn- add-outliers - "Add `n` outliers (times `scale` value spikes) to field `k`. `n` can be either percentage or count, determined by - `mode`." - ([mode n k xs] (add-outliers mode n 10 k xs)) - ([mode n scale k xs] - (if (= mode :share) - (for [x xs] - (if (< (rand) n) - (update x k * scale) - x)) - (let [candidate-idxs (keep (fn [[idx x]] - (when (k x) - idx)) - (m/indexed xs)) - ; Note: since we are sampling with replacement there is a small chance - ; single index gets chosen multiple times. - outlier-idx? (set (repeatedly n #(rand-nth candidate-idxs)))] - (for [[idx x] (m/indexed xs)] - (if (outlier-idx? idx) - (update x k * scale) - x)))))) - -(defn create-random-data [] - {:post [(map? %) - (= (count (:people %)) (:people num-rows-to-create)) - (= (count (:products %)) (:products num-rows-to-create)) - (every? keyword? (keys %)) - (every? sequential? (vals %))]} - (let [{:keys [products people]} num-rows-to-create] - (printf "Generating random data: %d people, %d products...\n" people products) - (let [products (for [product (create-randoms products random-product)] - (product-add-reviews product)) - people (vec (for [person (create-randoms people random-person)] - (person-add-orders products person)))] - {:people (map #(dissoc % :orders) people) - :products (map #(dissoc % :reviews) products) - :reviews (mapcat :reviews products) - :orders (->> people - (mapcat :orders) - (add-autocorrelation :quantity) - (add-outliers :share 0.01 :quantity) - (add-outliers :count 5 :discount) - (add-increasing-variance :total) - (add-seasonality #(.getMonth ^java.util.Date (:created_at %)) - :quantity {0 0.6 - 1 0.5 - 2 0.3 - 3 0.9 - 4 1.3 - 5 1.9 - 6 1.5 - 7 2.1 - 8 1.5 - 9 1.7 - 10 0.9 - 11 0.6}))}))) - -;;; # LOADING THE DATA - -(defn- create-table-sql [table-name field->type] - {:pre [(keyword? table-name) - (map? field->type) - (every? keyword? (keys field->type)) - (every? string? (vals field->type))] - :post [(string? %)]} - (format "CREATE TABLE \"%s\" (\"ID\" BIGINT AUTO_INCREMENT, %s, PRIMARY KEY (\"ID\"));" - (str/upper-case (name table-name)) - (apply str (->> (for [[field type] (seq field->type)] - (format "\"%s\" %s" (str/upper-case (name field)) type)) - (interpose ", "))))) - -(def ^:private ^:const tables - {:people {:name "VARCHAR(255)" - :email "VARCHAR(255)" - :password "VARCHAR(255)" - :birth_date "DATE" - :address "VARCHAR(255)" - :zip "CHAR(5)" - :city "VARCHAR(255)" - :state "CHAR(2)" - :latitude "FLOAT" - :longitude "FLOAT" - :source "VARCHAR(255)" - :created_at "DATETIME"} - :products {:ean "CHAR(13)" - :title "VARCHAR(255)" - :category "VARCHAR(255)" - :vendor "VARCHAR(255)" - :price "FLOAT" - :rating "FLOAT" - :created_at "DATETIME"} - :orders {:user_id "INTEGER" - :product_id "INTEGER" - :subtotal "FLOAT" - :tax "FLOAT" - :total "FLOAT" - :discount "FLOAT" - :created_at "DATETIME" - :quantity "INTEGER"} - :reviews {:product_id "INTEGER" - :reviewer "VARCHAR(255)" - :rating "SMALLINT" - :body "TEXT" - :created_at "DATETIME"}}) - -(def ^:private ^:const fks - [{:source-table "ORDERS" - :field "USER_ID" - :dest-table "PEOPLE"} - {:source-table "ORDERS" - :field "PRODUCT_ID" - :dest-table "PRODUCTS"} - {:source-table "REVIEWS" - :field "PRODUCT_ID" - :dest-table "PRODUCTS"}]) - -(def ^:private ^:const metabase-metadata - {:orders {:description "This is a confirmed order for a product from a user." - :columns {:created_at {:description "The date and time an order was submitted."} - :id {:description "This is a unique ID for the product. It is also called the “Invoice number†or “Confirmation number†in customer facing emails and screens."} - :product_id {:description "The product ID. This is an internal identifier for the product, NOT the SKU."} - :subtotal {:description "The raw, pre-tax cost of the order. Note that this might be different in the future from the product price due to promotions, credits, etc."} - :tax {:description (str "This is the amount of local and federal taxes that are collected on the purchase. Note that other governmental fees " - "on some products are not included here, but instead are accounted for in the subtotal.")} - :total {:description "The total billed amount."} - :user_id {:description (str "The id of the user who made this order. Note that in some cases where an order was created on behalf " - "of a customer who phoned the order in, this might be the employee who handled the request.")} - :quantity {:description "Number of products bought."} - :discount {:description "Discount amount."}}} - :people {:description "This is a user account. Note that employees and customer support staff will have accounts." - :columns {:address {:description "The street address of the account’s billing address"} - :birth_date {:description "The date of birth of the user"} - :city {:description "The city of the account’s billing address"} - :created_at {:description "The date the user record was created. Also referred to as the user’s \"join date\""} - :email {:description "The contact email for the account."} - :id {:description "A unique identifier given to each user."} - :latitude {:description "This is the latitude of the user on sign-up. It might be updated in the future to the last seen location."} - :longitude {:description "This is the longitude of the user on sign-up. It might be updated in the future to the last seen location."} - :name {:description "The name of the user who owns an account"} - :password {:description "This is the salted password of the user. It should not be visible"} - :source {:description "The channel through which we acquired this user. Valid values include: Affiliate, Facebook, Google, Organic and Twitter"} - :state {:description "The state or province of the account’s billing address"} - :zip {:description "The postal code of the account’s billing address" - :special_type "type/ZipCode"}}} - :products {:description "This is our product catalog. It includes all products ever sold by the Sample Company." - :columns {:category {:description "The type of product, valid values include: Doohicky, Gadget, Gizmo and Widget"} - :created_at {:description "The date the product was added to our catalog."} - :ean {:description "The international article number. A 13 digit number uniquely identifying the product."} - :id {:description "The numerical product number. Only used internally. All external communication should use the title or EAN."} - :price {:description "The list price of the product. Note that this is not always the price the product sold for due to discounts, promotions, etc."} - :rating {:description "The average rating users have given the product. This ranges from 1 - 5"} - :title {:description "The name of the product as it should be displayed to customers."} - :vendor {:description "The source of the product."}}} - :reviews {:description "These are reviews our customers have left on products. Note that these are not tied to orders so it is possible people have reviewed products they did not purchase from us." - :columns {:body {:description "The review the user left. Limited to 2000 characters." - :special_type "type/Description"} - :created_at {:description "The day and time a review was written by a user."} - :id {:description "A unique internal identifier for the review. Should not be used externally."} - :product_id {:description "The product the review was for"} - :rating {:description "The rating (on a scale of 1-5) the user left."} - :reviewer {:description "The user who left the review"}}}}) - -(defn create-h2-db - ([filename] - (create-h2-db filename (create-random-data))) - ([filename data] - (println "Deleting existing db...") - (io/delete-file (str filename ".mv.db") :silently) - (io/delete-file (str filename ".trace.db") :silently) - (println "Creating db...") - (let [db (dbspec/h2 {:db (format (str "file:%s;UNDO_LOG=0;CACHE_SIZE=131072;QUERY_CACHE_SIZE=128;COMPRESS=TRUE;" - "MULTI_THREADED=TRUE;MVCC=TRUE;DEFRAG_ALWAYS=TRUE;MAX_COMPACT_TIME=5000;" - "ANALYZE_AUTO=100") - filename)})] - (doseq [[table-name field->type] (seq tables)] - (jdbc/execute! db [(create-table-sql table-name field->type)])) - - ;; Add FK constraints - (println "Adding FKs...") - (doseq [{:keys [source-table field dest-table]} fks] - (jdbc/execute! db [(format "ALTER TABLE \"%s\" ADD CONSTRAINT \"FK_%s_%s_%s\" FOREIGN KEY (\"%s\") REFERENCES \"%s\" (\"ID\");" - source-table - source-table field dest-table - field - dest-table)])) - - ;; Insert the data - (println "Inserting data...") - (doseq [[table rows] (seq data)] - (assert (keyword? table)) - (assert (sequential? rows)) - (let [table-name (str/upper-case (name table))] - (println (format "Inserting %d rows into %s..." (count rows) table-name)) - (jdbc/insert-multi! db table-name (for [row rows] - (into {} (for [[k v] (seq row)] - {(str/upper-case (name k)) v})))))) - - ;; Insert the _metabase_metadata table - (println "Inserting _metabase_metadata...") - (jdbc/execute! db ["CREATE TABLE \"_METABASE_METADATA\" (\"KEYPATH\" VARCHAR(255), \"VALUE\" VARCHAR(255), PRIMARY KEY (\"KEYPATH\"));"]) - (jdbc/insert-multi! db "_METABASE_METADATA" (reduce concat (for [[table-name {table-description :description, columns :columns}] metabase-metadata] - (let [table-name (str/upper-case (name table-name))] - (conj (for [[column-name kvs] columns - [k v] kvs] - {:keypath (format "%s.%s.%s" table-name (str/upper-case (name column-name)) (name k)) - :value v}) - {:keypath (format "%s.description" table-name) - :value table-description}))))) - - ;; Create the 'GUEST' user - (println "Preparing database for export...") - (jdbc/execute! db ["CREATE USER GUEST PASSWORD 'guest';"]) - (doseq [table (conj (keys data) "_METABASE_METADATA")] - (jdbc/execute! db [(format "GRANT SELECT ON %s TO GUEST;" (str/upper-case (name table)))])) - - (println "Done.")))) - -(defn -main [& [filename]] - (let [filename (or filename sample-dataset-filename)] - (printf "Writing sample dataset to %s...\n" filename) - (create-h2-db filename) - (System/exit 0))) diff --git a/modules/drivers/bigquery/test/metabase/driver/bigquery/query_processor_test.clj b/modules/drivers/bigquery/test/metabase/driver/bigquery/query_processor_test.clj index 1e8b4355c80..ddb87a3d056 100644 --- a/modules/drivers/bigquery/test/metabase/driver/bigquery/query_processor_test.clj +++ b/modules/drivers/bigquery/test/metabase/driver/bigquery/query_processor_test.clj @@ -1,6 +1,5 @@ (ns metabase.driver.bigquery.query-processor-test - (:require [clj-time.core :as time] - [clojure.test :refer :all] + (:require [clojure.test :refer :all] [honeysql.core :as hsql] [java-time :as t] [metabase @@ -150,7 +149,7 @@ "A UTC date is returned, we should read/return it as UTC") (is (= "2018-08-31T00:00:00-05:00" - (tu.tz/with-jvm-tz (time/time-zone-for-id "America/Chicago") + (tu.tz/with-system-timezone-id "America/Chicago" (tt/with-temp* [Database [db {:engine :bigquery :details (assoc (:details (data/db)) :use-jvm-timezone true)}]] @@ -160,7 +159,7 @@ "the correct date is compared")) (is (= "2018-08-31T00:00:00+07:00" - (tu.tz/with-jvm-tz (time/time-zone-for-id "Asia/Jakarta") + (tu.tz/with-system-timezone-id "Asia/Jakarta" (tt/with-temp* [Database [db {:engine :bigquery :details (assoc (:details (data/db)) :use-jvm-timezone true)}]] diff --git a/modules/drivers/bigquery/test/metabase/test/data/bigquery.clj b/modules/drivers/bigquery/test/metabase/test/data/bigquery.clj index 4514f855471..34bc9b3ab04 100644 --- a/modules/drivers/bigquery/test/metabase/test/data/bigquery.clj +++ b/modules/drivers/bigquery/test/metabase/test/data/bigquery.clj @@ -14,7 +14,6 @@ [interface :as tx] [sql :as sql.tx]] [metabase.util - [date :as du] [date-2 :as u.date] [schema :as su]] [schema.core :as s]) @@ -130,7 +129,7 @@ "Convert the HoneySQL form we normally use to wrap a `Timestamp` to a Google `DateTime`." [{[{s :literal}] :args}] {:pre [(string? s) (seq s)]} - (DateTime. (du/->Timestamp (str/replace s #"'" "")))) + (DateTime. (t/to-java-date (u.date/parse (str/replace s #"'" ""))))) (defn- insert-data! [^String dataset-id, ^String table-id, row-maps] diff --git a/modules/drivers/druid/test/metabase/driver/druid_test.clj b/modules/drivers/druid/test/metabase/driver/druid_test.clj index 75f027c5724..b047f3650fd 100644 --- a/modules/drivers/druid/test/metabase/driver/druid_test.clj +++ b/modules/drivers/druid/test/metabase/driver/druid_test.clj @@ -1,6 +1,5 @@ (ns metabase.driver.druid-test (:require [cheshire.core :as json] - [clj-time.core :as time] [clojure.test :refer :all] [expectations :refer [expect]] [java-time :as t] @@ -51,7 +50,7 @@ (tu/with-temporary-setting-values [report-timezone "America/Los_Angeles"] (is (= expected (table-rows-sample)))) - (tu.tz/with-jvm-tz (time/time-zone-for-id "America/Chicago") + (tu.tz/with-system-timezone-id "America/Chicago" (is (= expected (table-rows-sample)))))))) diff --git a/modules/drivers/googleanalytics/src/metabase/driver/googleanalytics/query_processor.clj b/modules/drivers/googleanalytics/src/metabase/driver/googleanalytics/query_processor.clj index 0db41b21276..1652dcd8fc4 100644 --- a/modules/drivers/googleanalytics/src/metabase/driver/googleanalytics/query_processor.clj +++ b/modules/drivers/googleanalytics/src/metabase/driver/googleanalytics/query_processor.clj @@ -7,12 +7,16 @@ [metabase.mbql.util :as mbql.u] [metabase.query-processor.store :as qp.store] [metabase.util - [date :as du] [date-2 :as u.date] [i18n :as ui18n :refer [deferred-tru tru]] [schema :as su]] + [metabase.util.date-2.parse :as u.date.parse] + [metabase.util.date-2.parse.builder :as u.date.parse.builder] [schema.core :as s]) - (:import [com.google.api.services.analytics.model GaData GaData$ColumnHeaders])) + (:import [com.google.api.services.analytics.model GaData GaData$ColumnHeaders] + java.time.DayOfWeek + java.time.format.DateTimeFormatter + org.threeten.extra.YearWeek)) (def ^:private ^:const earliest-date "2005-01-01") (def ^:private ^:const latest-date "today") @@ -407,16 +411,26 @@ (defn- parse-number [s] (edn/read-string (str/replace s #"^0+(.+)$" "$1"))) +(def ^:private ^DateTimeFormatter iso-year-week-formatter + (u.date.parse.builder/formatter + (u.date.parse.builder/value :iso/week-based-year 4) + (u.date.parse.builder/value :iso/week-of-week-based-year 2))) + +(defn- parse-iso-year-week [^String s] + (when s + (-> (YearWeek/from (.parse iso-year-week-formatter s)) + (.atDay DayOfWeek/MONDAY)))) + (def ^:private ga-dimension->date-format-fn {"ga:minute" parse-number - "ga:dateHour" (partial du/parse-date "yyyyMMddHH") + "ga:dateHour" (partial u.date.parse/parse-with-formatter "yyyyMMddHH") "ga:hour" parse-number - "ga:date" (partial du/parse-date "yyyyMMdd") + "ga:date" (partial u.date.parse/parse-with-formatter "yyyyMMdd") "ga:dayOfWeek" (comp inc parse-number) "ga:day" parse-number - "ga:isoYearIsoWeek" (partial du/parse-date "xxxxww") + "ga:isoYearIsoWeek" parse-iso-year-week "ga:week" parse-number - "ga:yearMonth" (partial du/parse-date "yyyyMM") + "ga:yearMonth" (partial u.date.parse/parse-with-formatter "yyyyMM") "ga:month" parse-number "ga:year" parse-number}) diff --git a/modules/drivers/googleanalytics/test/metabase/driver/googleanalytics/query_processor_test.clj b/modules/drivers/googleanalytics/test/metabase/driver/googleanalytics/query_processor_test.clj index c06a1dd2ce5..08ba3f92724 100644 --- a/modules/drivers/googleanalytics/test/metabase/driver/googleanalytics/query_processor_test.clj +++ b/modules/drivers/googleanalytics/test/metabase/driver/googleanalytics/query_processor_test.clj @@ -18,7 +18,7 @@ (deftest iso-year-iso-week-test (testing "Make sure we properly parse isoYearIsoWeeks (#9244)" - (is (= #inst "2018-12-31T00:00:00.000000000-00:00" + (is (= #t "2018-12-31" ((#'ga.qp/ga-dimension->date-format-fn "ga:isoYearIsoWeek") "201901"))))) (deftest filter-test diff --git a/modules/drivers/mongo/src/metabase/driver/mongo.clj b/modules/drivers/mongo/src/metabase/driver/mongo.clj index 4f561f637a3..048ce67adac 100644 --- a/modules/drivers/mongo/src/metabase/driver/mongo.clj +++ b/modules/drivers/mongo/src/metabase/driver/mongo.clj @@ -4,6 +4,7 @@ [core :as json] [generate :as json.generate]] [clojure.tools.logging :as log] + [java-time :as t] [metabase [driver :as driver] [util :as u]] @@ -12,18 +13,26 @@ [metabase.driver.mongo [query-processor :as qp] [util :refer [with-mongo-connection]]] - [metabase.query-processor.store :as qp.store] + [metabase.plugins.classloader :as classloader] + [metabase.query-processor + [store :as qp.store] + [timezone :as qp.timezone]] [monger [collection :as mc] [command :as cmd] - [conversion :as conv] + [conversion :as m.conversion] [db :as mdb]] [schema.core :as s] [taoensso.nippy :as nippy]) (:import com.mongodb.DB + [java.time Instant LocalDate LocalDateTime LocalTime OffsetDateTime OffsetTime ZonedDateTime] org.bson.BsonUndefined org.bson.types.ObjectId)) +;; See http://clojuremongodb.info/articles/integration.html Loading this namespace will load appropriate Monger +;; integrations with Cheshire. +(classloader/require 'monger.json) + ;; JSON Encoding (etc.) ;; Encode BSON undefined like `nil` @@ -43,7 +52,7 @@ [_ details] (with-mongo-connection [^DB conn, details] (= (float (-> (cmd/db-stats conn) - (conv/from-db-object :keywordize) + (m.conversion/from-db-object :keywordize) :ok)) 1.0))) @@ -199,3 +208,43 @@ (defmethod driver/execute-query :mongo [_ query] (qp/execute-query query)) + +;; It seems to be the case that the only thing BSON supports is DateTime which is basically the equivalent of Instant; +;; for the rest of the types, we'll have to fake it +(extend-protocol m.conversion/ConvertToDBObject + Instant + (to-db-object [t] + (org.bson.BsonDateTime. (t/to-millis-from-epoch t))) + + LocalDate + (to-db-object [t] + (m.conversion/to-db-object (t/local-date-time t (t/local-time 0)))) + + LocalDateTime + (to-db-object [t] + ;; QP store won't be bound when loading test data for example. + (m.conversion/to-db-object (t/instant t (t/zone-id (try + (qp.timezone/results-timezone-id) + (catch Throwable _ + "UTC")))))) + + LocalTime + (to-db-object [t] + (m.conversion/to-db-object (t/local-date-time (t/local-date "1970-01-01") t))) + + OffsetDateTime + (to-db-object [t] + (m.conversion/to-db-object (t/instant t))) + + OffsetTime + (to-db-object [t] + (m.conversion/to-db-object (t/offset-date-time (t/local-date "1970-01-01") t (t/zone-offset t)))) + + ZonedDateTime + (to-db-object [t] + (m.conversion/to-db-object (t/instant t)))) + +(extend-protocol m.conversion/ConvertFromDBObject + java.util.Date + (from-db-object [t _] + (t/instant t))) diff --git a/modules/drivers/mongo/src/metabase/driver/mongo/query_processor.clj b/modules/drivers/mongo/src/metabase/driver/mongo/query_processor.clj index b75c289cef1..3b8d4de404b 100644 --- a/modules/drivers/mongo/src/metabase/driver/mongo/query_processor.clj +++ b/modules/drivers/mongo/src/metabase/driver/mongo/query_processor.clj @@ -25,18 +25,11 @@ [schema :as su]] [monger [collection :as mc] - [conversion :as m.conversion] - json [operators :refer :all]] [schema.core :as s]) (:import metabase.models.field.FieldInstance org.bson.types.ObjectId)) -;; See http://clojuremongodb.info/articles/integration.html Loading this namespace will load appropriate Monger -;; integrations with Cheshire. The comment below is to fool `cljr-clean-ns` into keeping the namespace in the -;; `:require` form above -(comment monger.json/keep-me) - ;;; +----------------------------------------------------------------------------------------------------------------+ ;;; | Schema | ;;; +----------------------------------------------------------------------------------------------------------------+ @@ -75,21 +68,6 @@ column names in the query (?)" [s/Keyword]) -;; TIMEZONE FIXME — or use codecs — https://mongodb.github.io/mongo-java-driver/3.0/bson/codecs/ -#_(extend-protocol m.conversion/ConvertToDBObject - java.time.LocalDate - (to-db-object [t]) - java.time.LocalTime - java.time.LocalDateTime - java.time.OffsetTime - java.time.OffsetDateTime - java.time.ZonedDateTime) - -(extend-protocol m.conversion/ConvertFromDBObject - java.util.Date - (from-db-object [t _] - (t/instant t))) - ;;; +----------------------------------------------------------------------------------------------------------------+ ;;; | QP Impl | ;;; +----------------------------------------------------------------------------------------------------------------+ diff --git a/modules/drivers/mongo/test/metabase/test/data/mongo.clj b/modules/drivers/mongo/test/metabase/test/data/mongo.clj index 70482e6895b..a5496769c8c 100644 --- a/modules/drivers/mongo/test/metabase/test/data/mongo.clj +++ b/modules/drivers/mongo/test/metabase/test/data/mongo.clj @@ -26,20 +26,12 @@ (keyword (:field-name field-definition)))] ;; Use map-indexed so we can get an ID for each row (index + 1) (doseq [[i row] (map-indexed (partial vector) rows)] - (let [row (for [v row] - ;; Conver all the java.sql.Timestamps to java.util.Date, because the Mongo driver insists on - ;; being obnoxious and going from using Timestamps in 2.x to Dates in 3.x - ;; - ;; TIMEZONE FIXME — stop using legacy types - (if (instance? java.sql.Timestamp v) - (java.util.Date. (.getTime ^java.sql.Timestamp v)) - v))] - (try - ;; Insert each row - (mc/insert mongo-db (name table-name) (assoc (zipmap field-names row) - :_id (inc i))) - ;; If row already exists then nothing to do - (catch com.mongodb.MongoException _)))))))) + (try + ;; Insert each row + (mc/insert mongo-db (name table-name) (assoc (zipmap field-names row) + :_id (inc i))) + ;; If row already exists then nothing to do + (catch com.mongodb.MongoException _))))))) (defmethod tx/format-name :mongo [_ table-or-field-name] diff --git a/modules/drivers/oracle/src/metabase/driver/oracle.clj b/modules/drivers/oracle/src/metabase/driver/oracle.clj index 3d6117da80d..1d52d362187 100644 --- a/modules/drivers/oracle/src/metabase/driver/oracle.clj +++ b/modules/drivers/oracle/src/metabase/driver/oracle.clj @@ -74,12 +74,7 @@ (when sid (str ":" sid)) (when service-name - (str "/" service-name))) - ;; By default the Oracle JDBC driver isn't compliant with JDBC standards -- instead of returning types like - ;; java.sql.Timestamp it returns wacky types like oracle.sql.TIMESTAMPT. By setting this property the JDBC driver - ;; will return the appropriate types. See this page for more details: - ;; http://docs.oracle.com/database/121/JJDBC/datacc.htm#sthref437 - :oracle.jdbc.J2EE13Compliant true} + (str "/" service-name)))} (dissoc details :host :port :sid :service-name))) (defmethod driver/can-connect? :oracle diff --git a/modules/drivers/oracle/test/metabase/driver/oracle_test.clj b/modules/drivers/oracle/test/metabase/driver/oracle_test.clj index 5c144075d2d..6acbea284b9 100644 --- a/modules/drivers/oracle/test/metabase/driver/oracle_test.clj +++ b/modules/drivers/oracle/test/metabase/driver/oracle_test.clj @@ -28,36 +28,32 @@ [toucan.util.test :as tt])) (deftest connection-details->spec-test - (are [message expected-spec details] (is (= expected-spec - (sql-jdbc.conn/connection-details->spec :oracle details)) - message) - "You should be able to connect with an SID" - {:classname "oracle.jdbc.OracleDriver" - :subprotocol "oracle:thin" - :subname "@localhost:1521:ORCL" - :oracle.jdbc.J2EE13Compliant true} - {:host "localhost" - :port 1521 - :sid "ORCL"} - - "You should be able to specify a Service Name with no SID" - {:classname "oracle.jdbc.OracleDriver" - :subprotocol "oracle:thin" - :subname "@localhost:1521/MyCoolService" - :oracle.jdbc.J2EE13Compliant true} - {:host "localhost" - :port 1521 - :service-name "MyCoolService"} - - "You should be able to specifiy a Service Name *and* an SID" - {:classname "oracle.jdbc.OracleDriver" - :subprotocol "oracle:thin" - :subname "@localhost:1521:ORCL/MyCoolService" - :oracle.jdbc.J2EE13Compliant true} - {:host "localhost" - :port 1521 - :service-name "MyCoolService" - :sid "ORCL"})) + (doseq [[message expected-spec details] + [["You should be able to connect with an SID" + {:classname "oracle.jdbc.OracleDriver" + :subprotocol "oracle:thin" + :subname "@localhost:1521:ORCL"} + {:host "localhost" + :port 1521 + :sid "ORCL"}] + ["You should be able to specify a Service Name with no SID" + {:classname "oracle.jdbc.OracleDriver" + :subprotocol "oracle:thin" + :subname "@localhost:1521/MyCoolService"} + {:host "localhost" + :port 1521 + :service-name "MyCoolService"}] + ["You should be able to specifiy a Service Name *and* an SID" + {:classname "oracle.jdbc.OracleDriver" + :subprotocol "oracle:thin" + :subname "@localhost:1521:ORCL/MyCoolService"} + {:host "localhost" + :port 1521 + :service-name "MyCoolService" + :sid "ORCL"}]]] + (is (= expected-spec + (sql-jdbc.conn/connection-details->spec :oracle details)) + message))) ;; no SID and not Service Name should throw an exception (expect diff --git a/modules/drivers/snowflake/test/metabase/driver/snowflake_test.clj b/modules/drivers/snowflake/test/metabase/driver/snowflake_test.clj index b488424345d..d767ded5677 100644 --- a/modules/drivers/snowflake/test/metabase/driver/snowflake_test.clj +++ b/modules/drivers/snowflake/test/metabase/driver/snowflake_test.clj @@ -13,7 +13,8 @@ [datasets :as datasets :refer [expect-with-driver]] [interface :as tx] [sql :as sql.tx]] - [metabase.test.data.sql.ddl :as ddl])) + [metabase.test.data.sql.ddl :as ddl] + [metabase.test.util.log :as tu.log])) ;; make sure we didn't break the code that is used to generate DDL statements when we add new test datasets (deftest ddl-statements-test @@ -90,11 +91,12 @@ (deftest can-connect-test (datasets/test-driver :snowflake - (let [can-connect? (fn [details] - (driver/can-connect? :snowflake details))] + (letfn [(can-connect? [details] + (driver/can-connect? :snowflake details))] (is (= true (can-connect? (:details (data/db)))) "can-connect? should return true for normal Snowflake DB details") (is (= false - (can-connect? (assoc (:details (data/db)) :db (tu/random-name)))) + (tu.log/suppress-output + (can-connect? (assoc (:details (data/db)) :db (tu/random-name))))) "can-connect? should return false for Snowflake databases that don't exist (#9041)")))) diff --git a/modules/drivers/snowflake/test/metabase/test/data/snowflake.clj b/modules/drivers/snowflake/test/metabase/test/data/snowflake.clj index 44c011ed4fc..f846a160215 100644 --- a/modules/drivers/snowflake/test/metabase/test/data/snowflake.clj +++ b/modules/drivers/snowflake/test/metabase/test/data/snowflake.clj @@ -111,5 +111,6 @@ (defmethod tx/id-field-type :snowflake [_] :type/Number) -(defmethod load-data/load-data! :snowflake [& args] +(defmethod load-data/load-data! :snowflake + [& args] (apply load-data/load-data-add-ids! args)) diff --git a/modules/drivers/sparksql/src/metabase/driver/hive_like.clj b/modules/drivers/sparksql/src/metabase/driver/hive_like.clj index a878c40345e..f1ae2c197d4 100644 --- a/modules/drivers/sparksql/src/metabase/driver/hive_like.clj +++ b/modules/drivers/sparksql/src/metabase/driver/hive_like.clj @@ -130,9 +130,9 @@ "Run the query itself." [driver {sql :query, :keys [params remark max-rows]} connection] (let [sql (str "-- " remark "\n" sql) - options {:identifiers identity - :as-arrays? true - :max-rows max-rows + options {:identifiers identity + :as-arrays? true + :max-rows max-rows :read-columns (partial sql-jdbc.execute/read-columns driver) :set-parameters (partial sql-jdbc.execute/set-parameters driver)}] (with-open [connection (jdbc/get-connection connection)] diff --git a/modules/drivers/sqlite/src/metabase/driver/sqlite.clj b/modules/drivers/sqlite/src/metabase/driver/sqlite.clj index 0cc213a5b2d..3d7d4a85446 100644 --- a/modules/drivers/sqlite/src/metabase/driver/sqlite.clj +++ b/modules/drivers/sqlite/src/metabase/driver/sqlite.clj @@ -160,7 +160,8 @@ [driver _ expr] (->date (sql.qp/->honeysql driver expr) (hx/literal "start of year"))) -(defmethod driver/date-add :sqlite [driver dt amount unit] +(defmethod driver/date-add :sqlite + [driver dt amount unit] (let [[multiplier sqlite-unit] (case unit :second [1 "seconds"] :minute [1 "minutes"] @@ -194,10 +195,12 @@ ;; ;; TODO - not sure why this doesn't need to be done in `->honeysql` as well? I think it's because the MBQL date values ;; are funneled through the `date` family of functions above -;; TIMESTAMP FIXME +;; +;; TIMESTAMP FIXME — this doesn't seem like the correct thing to do for non-Dates. I think params only support dates +;; rn however (s/defmethod sql/->prepared-substitution [:sqlite Temporal] :- sql/PreparedStatementSubstitution [_ date] - ;; for anything that's a Date (usually a java.sql.Timestamp) convert it to a yyyy-MM-dd formatted date literal + ;; for anything that's a Temporal value convert it to a yyyy-MM-dd formatted date literal ;; string For whatever reason the SQL generated from parameters ends up looking like `WHERE date(some_field) = ?` ;; sometimes so we need to use just the date rather than a full ISO-8601 string (sql/make-stmt-subs "?" [(t/format "yyyy-MM-dd" date)])) @@ -209,29 +212,46 @@ ;; See https://sqlite.org/lang_datefunc.html +;; MEGA HACK +;; +;; if the time portion is zeroed out generate a date() instead, because SQLite isn't smart enough to compare DATEs +;; and DATETIMEs in a way that could be considered to make any sense whatsoever, e.g. +;; +;; date('2019-12-03') < datetime('2019-12-03 00:00') +(defn- zero-time? [t] + (= (t/local-time t) (t/local-time 0))) + (defmethod sql.qp/->honeysql [:sqlite LocalDate] [_ t] (hsql/call :date (hx/literal (u.date/format-sql t)))) (defmethod sql.qp/->honeysql [:sqlite LocalDateTime] - [_ t] - (hsql/call :datetime (hx/literal (u.date/format-sql t)))) + [driver t] + (if (zero-time? t) + (sql.qp/->honeysql driver (t/local-date t)) + (hsql/call :datetime (hx/literal (u.date/format-sql t))))) (defmethod sql.qp/->honeysql [:sqlite LocalTime] [_ t] (hsql/call :time (hx/literal (u.date/format-sql t)))) (defmethod sql.qp/->honeysql [:sqlite OffsetDateTime] - [_ t] - (hsql/call :datetime (hx/literal (u.date/format-sql t)))) + [driver t] + (if (zero-time? t) + (sql.qp/->honeysql driver (t/local-date t)) + (hsql/call :datetime (hx/literal (u.date/format-sql t))))) (defmethod sql.qp/->honeysql [:sqlite OffsetTime] [_ t] (hsql/call :time (hx/literal (u.date/format-sql t)))) (defmethod sql.qp/->honeysql [:sqlite ZonedDateTime] - [_ t] - (hsql/call :datetime (hx/literal (u.date/format-sql t)))) + [driver t] + (println "t:" t) ; NOCOMMIT + (println "(t/local-date t):" (t/local-date t)) ; NOCOMMIT + (if (zero-time? t) + (sql.qp/->honeysql driver (t/local-date t)) + (hsql/call :datetime (hx/literal (u.date/format-sql t))))) ;; SQLite `LIKE` clauses are case-insensitive by default, and thus cannot be made case-sensitive. So let people know ;; we have this 'feature' so the frontend doesn't try to present the option to you. diff --git a/modules/drivers/sqlite/test/metabase/driver/sqlite_test.clj b/modules/drivers/sqlite/test/metabase/driver/sqlite_test.clj index a07138f1291..33284517ea4 100644 --- a/modules/drivers/sqlite/test/metabase/driver/sqlite_test.clj +++ b/modules/drivers/sqlite/test/metabase/driver/sqlite_test.clj @@ -1,8 +1,34 @@ (ns metabase.driver.sqlite-test - (:require [expectations :refer [expect]] - [metabase.test.data.datasets :refer [expect-with-driver]] - [metabase.test.util :as tu])) + (:require [clojure.test :refer :all] + [metabase.query-processor-test :as qp.test] + [metabase.test + [data :as data] + [util :as tu]] + [metabase.test.data.datasets :as datasets])) -(expect-with-driver :sqlite - "UTC" - (tu/db-timezone-id)) +(deftest timezone-id-test + (datasets/test-driver :sqlite + (is (= "UTC" + (tu/db-timezone-id))))) + +(deftest filter-by-date-test + "Make sure filtering against a LocalDate works correctly in SQLite" + (datasets/test-driver :sqlite + (is (= [[225 "2014-03-04T00:00:00Z"] + [409 "2014-03-05T00:00:00Z"] + [917 "2014-03-05T00:00:00Z"] + [995 "2014-03-05T00:00:00Z"] + [159 "2014-03-06T00:00:00Z"] + [951 "2014-03-06T00:00:00Z"]] + (qp.test/rows + (data/run-mbql-query checkins + {:fields [$id $date] + :filter [:and + [:>= $date "2014-03-04"] + [:<= $date "2014-03-06"]] + :order-by [[:asc $date]]})) + (qp.test/rows + (data/run-mbql-query checkins + {:fields [$id $date] + :filter [:between $date "2014-03-04" "2014-03-07"] + :order-by [[:asc $date]]})))))) diff --git a/modules/drivers/sqlite/test/metabase/test/data/sqlite.clj b/modules/drivers/sqlite/test/metabase/test/data/sqlite.clj index ab04fa090ca..805bdb14bed 100644 --- a/modules/drivers/sqlite/test/metabase/test/data/sqlite.clj +++ b/modules/drivers/sqlite/test/metabase/test/data/sqlite.clj @@ -1,57 +1,31 @@ (ns metabase.test.data.sqlite - (:require [honeysql.core :as hsql] - [metabase.test.data + (:require [metabase.test.data [interface :as tx] [sql :as sql.tx] [sql-jdbc :as sql-jdbc.tx]] - [metabase.test.data.sql-jdbc - [execute :as execute] - [load-data :as load-data]] - [metabase.util - [date :as du] - [honeysql-extensions :as hx]])) + [metabase.test.data.sql-jdbc.execute :as execute])) (sql-jdbc.tx/add-test-extensions! :sqlite) (defmethod tx/dbdef->connection-details :sqlite [_ context dbdef] {:db (str (tx/escaped-name dbdef) ".sqlite")}) -(defmethod sql.tx/field-base-type->sql-type [:sqlite :type/BigInteger] [_ _] "BIGINT") -(defmethod sql.tx/field-base-type->sql-type [:sqlite :type/Boolean] [_ _] "BOOLEAN") -(defmethod sql.tx/field-base-type->sql-type [:sqlite :type/Date] [_ _] "DATE") -(defmethod sql.tx/field-base-type->sql-type [:sqlite :type/DateTime] [_ _] "DATETIME") -(defmethod sql.tx/field-base-type->sql-type [:sqlite :type/Decimal] [_ _] "DECIMAL") -(defmethod sql.tx/field-base-type->sql-type [:sqlite :type/Float] [_ _] "DOUBLE") -(defmethod sql.tx/field-base-type->sql-type [:sqlite :type/Integer] [_ _] "INTEGER") -(defmethod sql.tx/field-base-type->sql-type [:sqlite :type/Text] [_ _] "TEXT") -(defmethod sql.tx/field-base-type->sql-type [:sqlite :type/Time] [_ _] "TIME") - -(defn- load-data-stringify-dates - "Our SQLite JDBC driver doesn't seem to like Dates/Timestamps so just convert them to strings before INSERTing them - into the Database." - [insert!] - (fn [rows] - (insert! (for [row rows] - (into {} (for [[k v] row] - [k (cond - ;; TIMEZONE FIXME - (instance? java.sql.Time v) - (hsql/call :time (hx/literal (du/format-time v "UTC"))) - - (instance? java.util.Date v) - (hsql/call :datetime (hx/literal (du/date->iso-8601 v))) - - :else v)])))))) +(doseq [[base-type sql-type] {:type/BigInteger "BIGINT" + :type/Boolean "BOOLEAN" + :type/Date "DATE" + :type/DateTime "DATETIME" + :type/Decimal "DECIMAL" + :type/Float "DOUBLE" + :type/Integer "INTEGER" + :type/Text "TEXT" + :type/Time "TIME"}] + (defmethod sql.tx/field-base-type->sql-type [:sqlite base-type] [_ _] sql-type)) (defmethod sql.tx/pk-sql-type :sqlite [_] "INTEGER") (defmethod execute/execute-sql! :sqlite [& args] (apply execute/sequentially-execute-sql! args)) -(let [load-data! (load-data/make-load-data-fn load-data-stringify-dates load-data/load-data-chunked)] - (defmethod load-data/load-data! :sqlite [& args] - (apply load-data! args))) - (defmethod sql.tx/drop-db-if-exists-sql :sqlite [& _] nil) (defmethod sql.tx/create-db-sql :sqlite [& _] nil) (defmethod sql.tx/add-fk-sql :sqlite [& _] nil) ; TODO - fix me diff --git a/modules/drivers/sqlserver/src/metabase/driver/sqlserver.clj b/modules/drivers/sqlserver/src/metabase/driver/sqlserver.clj index 61d81074322..27553336d10 100644 --- a/modules/drivers/sqlserver/src/metabase/driver/sqlserver.clj +++ b/modules/drivers/sqlserver/src/metabase/driver/sqlserver.clj @@ -85,14 +85,14 @@ user) :instanceName instance :encrypt (boolean ssl) - ;; only crazy people would want this. Seehttps://docs.microsoft.com/en-us/sql/connect/jdbc/configuring-how-java-sql-time-values-are-sent-to-the-server?view=sql-server-ver15 + ;; only crazy people would want this. See https://docs.microsoft.com/en-us/sql/connect/jdbc/configuring-how-java-sql-time-values-are-sent-to-the-server?view=sql-server-ver15 :sendTimeAsDatetime false} ;; only include `port` if it is specified; leave out for dynamic port: see ;; https://github.com/metabase/metabase/issues/7597 (merge (when port {:port port})) (sql-jdbc.common/handle-additional-options details, :seperator-style :semicolon))) - +;; See https://docs.microsoft.com/en-us/sql/t-sql/functions/datepart-transact-sql?view=sql-server-ver15 (defn- date-part [unit expr] (hsql/call :datepart (hsql/raw (name unit)) expr)) @@ -122,15 +122,9 @@ [_ _ expr] (date-part :hour expr)) -;; jTDS is wack; I sense an ongoing theme here. It returns DATEs as strings instead of as java.sql.Dates like every -;; other SQL DB we support. Work around that by casting to DATE for truncation then back to DATETIME so we get the -;; type we want. -;; -;; TODO - I'm not sure we still need to do this now that we're using the official Microsoft JDBC driver. Maybe we can -;; simplify this now? (defmethod sql.qp/date [:sqlserver :day] [_ _ expr] - (hx/->datetime (hx/->date expr))) + (hx/->date expr)) (defmethod sql.qp/date [:sqlserver :day-of-week] [_ _ expr] @@ -183,8 +177,8 @@ [_ _ expr] (hsql/call :datefromparts (hx/year expr) 1 1)) - -(defmethod driver/date-add :sqlserver [_ dt amount unit] +(defmethod driver/date-add :sqlserver + [_ dt amount unit] (date-add unit amount dt)) (defmethod sql.qp/unix-timestamp->timestamp [:sqlserver :seconds] @@ -194,10 +188,12 @@ ;; Work around this by converting the timestamps to minutes instead before calling DATEADD(). (date-add :minute (hx// expr 60) (hx/literal "1970-01-01"))) -(defmethod sql.qp/apply-top-level-clause [:sqlserver :limit] [_ _ honeysql-form {value :limit}] +(defmethod sql.qp/apply-top-level-clause [:sqlserver :limit] + [_ _ honeysql-form {value :limit}] (assoc honeysql-form :modifiers [(format "TOP %d" value)])) -(defmethod sql.qp/apply-top-level-clause [:sqlserver :page] [_ _ honeysql-form {{:keys [items page]} :page}] +(defmethod sql.qp/apply-top-level-clause [:sqlserver :page] + [_ _ honeysql-form {{:keys [items page]} :page}] (assoc honeysql-form :offset (hsql/raw (format "%d ROWS FETCH NEXT %d ROWS ONLY" (* items (dec page)) items)))) diff --git a/modules/drivers/sqlserver/test/metabase/driver/sqlserver_test.clj b/modules/drivers/sqlserver/test/metabase/driver/sqlserver_test.clj index 5af23131e28..2453675e06a 100644 --- a/modules/drivers/sqlserver/test/metabase/driver/sqlserver_test.clj +++ b/modules/drivers/sqlserver/test/metabase/driver/sqlserver_test.clj @@ -145,24 +145,28 @@ :limit 5} :limit 3})))) -;; Make sure datetime bucketing functions work properly with languages that format dates like yyyy-dd-MM instead of -;; yyyy-MM-dd (i.e. not American English) (#9057) -(datasets/expect-with-driver :sqlserver - [{:my-date #inst "2019-02-01T00:00:00.000-00:00"}] - ;; we're doing things here with low-level calls to HoneySQL (emulating what the QP does) instead of using normal QP - ;; pathways because `SET LANGUAGE` doesn't seem to persist to subsequent executions so to test that things are - ;; working we need to add to in from of the query we're trying to check - (jdbc/with-db-transaction [t-conn (sql-jdbc.conn/connection-details->spec :sqlserver - (tx/dbdef->connection-details :sqlserver :db {:database-name "test-data"}))] - (try - (jdbc/execute! t-conn "CREATE TABLE temp (d DATETIME2);") - (jdbc/execute! t-conn ["INSERT INTO temp (d) VALUES (?)" #inst "2019-02-08T00:00:00Z"]) - (jdbc/query t-conn (let [[sql & args] (hsql/format {:select [[(sql.qp/date :sqlserver :month :temp.d) :my-date]] - :from [:temp]} - :quoting :ansi, :allow-dashed-names? true)] - (cons (str "SET LANGUAGE Italian; " sql) args))) - ;; rollback transaction so `temp` table gets discarded - (finally (.rollback (jdbc/get-connection t-conn)))))) +(deftest locale-bucketing-test + (datasets/test-driver :sqlserver + (testing (str "Make sure datetime bucketing functions work properly with languages that format dates like " + "yyyy-dd-MM instead of yyyy-MM-dd (i.e. not American English) (#9057)") + ;; we're doing things here with low-level calls to HoneySQL (emulating what the QP does) instead of using normal QP + ;; pathways because `SET LANGUAGE` doesn't seem to persist to subsequent executions so to test that things are + ;; working we need to add to in from of the query we're trying to check + (jdbc/with-db-transaction [t-conn (sql-jdbc.conn/connection-details->spec :sqlserver + (tx/dbdef->connection-details :sqlserver :db {:database-name "test-data"}))] + (try + (jdbc/execute! t-conn "CREATE TABLE temp (d DATETIME2);") + (jdbc/execute! t-conn ["INSERT INTO temp (d) VALUES (?)" #t "2019-02-08T00:00:00Z"]) + (let [[sql & args] (hsql/format {:select [[(sql.qp/date :sqlserver :month :temp.d) :my-date]] + :from [:temp]} + :quoting :ansi, :allow-dashed-names? true) + result (jdbc/query t-conn (cons (str "SET LANGUAGE Italian; " sql) args) + {:read-columns (partial sql-jdbc.execute/read-columns :sqlserver) + :set-parameters (partial sql-jdbc.execute/set-parameters :sqlserver)})] + (is (= [{:my-date #t "2019-02-01"}] + result))) + ;; rollback transaction so `temp` table gets discarded + (finally (.rollback (jdbc/get-connection t-conn)))))))) (defn- query [sql-args] (jdbc/query diff --git a/modules/drivers/vertica/src/metabase/driver/vertica.clj b/modules/drivers/vertica/src/metabase/driver/vertica.clj index 904b381f5e3..66eff36421c 100644 --- a/modules/drivers/vertica/src/metabase/driver/vertica.clj +++ b/modules/drivers/vertica/src/metabase/driver/vertica.clj @@ -13,7 +13,6 @@ [metabase.driver.sql-jdbc.execute.legacy-impl :as legacy] [metabase.driver.sql.query-processor :as sql.qp] [metabase.util - [date :as du] [date-2 :as u.date] [honeysql-extensions :as hx] [i18n :refer [trs]]]) @@ -57,12 +56,13 @@ [_ _ expr] (hsql/call :to_timestamp expr)) +;; TODO - not sure if needed or not (defn- cast-timestamp "Vertica requires stringified timestamps (what Date/DateTime/Timestamps are converted to) to be cast as timestamps before date operations can be performed. This function will add that cast if it is a timestamp, otherwise this is a - noop." + no-op." [expr] - (if (du/is-temporal? expr) + (if (instance? java.time.temporal.Temporal expr) (hx/cast :timestamp expr) expr)) diff --git a/project.clj b/project.clj index f4202157b97..fec9d4dc843 100644 --- a/project.clj +++ b/project.clj @@ -64,7 +64,6 @@ commons-io slingshot]] [clojure.java-time "0.3.2"] ; Java 8 java.time wrapper - [clj-time "0.15.1"] ; Joda-Time wrapper [clojurewerkz/quartzite "2.1.0" ; scheduling library :exclusions [c3p0]] [colorize "0.1.1" :exclusions [org.clojure/clojure]] ; string output with ANSI color codes (for logging) @@ -321,14 +320,6 @@ {:auto-clean true :aot :all} - ;; generate sample dataset with `lein generate-sample-dataset` - :generate-sample-dataset - {:dependencies - [[faker "0.3.2"] ; Fake data generator -- port of Perl/Ruby library - [jdistlib "0.5.1" :exclusions [com.github.wendykierp/JTransforms]]] ; Distribution statistic tests - :source-paths ["lein-commands/sample-dataset"] - :main ^:skip-aot metabase.sample-dataset.generate} - ;; lein strip-and-compress my-plugin.jar [path/to/metabase.jar] ;; strips classes from my-plugin.jar that already exist in other JAR and recompresses with higher compression ratio. ;; Second arg (other JAR) is optional; defaults to target/uberjar/metabase.jar diff --git a/resources/data_readers.clj b/resources/data_readers.clj new file mode 100644 index 00000000000..ce21fdd8412 --- /dev/null +++ b/resources/data_readers.clj @@ -0,0 +1 @@ +{t metabase.util.date-2/parse} diff --git a/src/metabase/api/activity.clj b/src/metabase/api/activity.clj index 187eca09980..cbf2fb4ae4d 100644 --- a/src/metabase/api/activity.clj +++ b/src/metabase/api/activity.clj @@ -17,7 +17,7 @@ (:topic activity))) (defn- activities->referenced-objects - "Get a map of model name to a set of referenced IDs in these ACTIVITIES. + "Get a map of model name to a set of referenced IDs in these `activities`. (activities->referenced-objects <some-activities>) -> {\"dashboard\" #{41 42 43}, \"card\" #{100 101}, ...}" [activities] @@ -48,7 +48,7 @@ nil)}))) ; don't care about other models (defn- add-model-exists-info - "Add `:model_exists` keys to ACTIVITIES, and `:exists` keys to nested dashcards where appropriate." + "Add `:model_exists` keys to `activities`, and `:exists` keys to nested dashcards where appropriate." [activities] (let [existing-objects (-> activities activities->referenced-objects referenced-objects->existing-objects)] (for [{:keys [model model_id], :as activity} activities] diff --git a/src/metabase/api/dataset.clj b/src/metabase/api/dataset.clj index e0a2158dfde..76228a96597 100644 --- a/src/metabase/api/dataset.clj +++ b/src/metabase/api/dataset.clj @@ -5,6 +5,7 @@ [clojure.string :as str] [clojure.tools.logging :as log] [compojure.core :refer [POST]] + [java-time :as t] [medley.core :as m] [metabase.api.common :as api] [metabase.mbql.schema :as mbql.s] @@ -19,7 +20,7 @@ [util :as qputil]] [metabase.query-processor.middleware.constraints :as constraints] [metabase.util - [date :as du] + [date-2 :as u.date] [export :as ex] [i18n :refer [trs tru]] [schema :as su]] @@ -113,7 +114,8 @@ (maybe-modify-date-values cols rows)) :headers {"Content-Type" (str (:content-type export-conf) "; charset=utf-8") "Content-Disposition" (format "attachment; filename=\"query_result_%s.%s\"" - (du/date->iso-8601) (:ext export-conf))}} + (u.date/format (t/zoned-date-time)) + (:ext export-conf))}} ;; failed query, send error message {:status (if (qp.error-type/server-error? error-type) 500 diff --git a/src/metabase/async/api_response.clj b/src/metabase/async/api_response.clj index 998e2e6a072..8b2aced2eab 100644 --- a/src/metabase/async/api_response.clj +++ b/src/metabase/async/api_response.clj @@ -4,7 +4,8 @@ {:status 200 :body (a/chan)} - and send strings (presumibly \n) as heartbeats to the client until the real results (a seq) is received, then stream + and send strings (presumibly +) as heartbeats to the client until the real results (a seq) is received, then stream that to the client." (:require [cheshire.core :as json] [clojure.core.async :as a] @@ -13,9 +14,7 @@ [compojure.response :refer [Sendable]] [metabase.middleware.exceptions :as mw.exceptions] [metabase.util :as u] - [metabase.util - [date :as du] - [i18n :as ui18n :refer [trs]]] + [metabase.util.i18n :as ui18n :refer [trs]] [ring.core.protocols :as ring.protocols] [ring.util.response :as response]) (:import clojure.core.async.impl.channels.ManyToManyChannel @@ -150,7 +149,7 @@ ;; Otherwise if we've been waiting longer than `absolute-max-keepalive-ms` it's time to call it quits exceeded-absolute-max-keepalive? (a/>! output-chan (TimeoutException. (trs "No response after waiting {0}. Canceling request." - (du/format-milliseconds absolute-max-keepalive-ms)))) + (u/format-milliseconds absolute-max-keepalive-ms)))) ;; if input-chan was unexpectedly closed log a message to that effect and return an appropriate error ;; rather than letting people wait forever diff --git a/src/metabase/automagic_dashboards/core.clj b/src/metabase/automagic_dashboards/core.clj index 62c4b11f009..460a5ff6107 100644 --- a/src/metabase/automagic_dashboards/core.clj +++ b/src/metabase/automagic_dashboards/core.clj @@ -3,14 +3,12 @@ heuristics." (:require [buddy.core.codecs :as codecs] [cheshire.core :as json] - [clj-time - [core :as t] - [format :as t.format]] [clojure [string :as str] [walk :as walk]] [clojure.math.combinatorics :as combo] [clojure.tools.logging :as log] + [java-time :as t] [kixi.stats [core :as stats] [math :as math]] @@ -39,12 +37,11 @@ [metabase.query-processor.util :as qp.util] [metabase.sync.analyze.classify :as classify] [metabase.util - [date :as date] + [date-2 :as u.date] [i18n :as ui18n :refer [deferred-tru trs tru]]] [ring.util.codec :as codec] [schema.core :as s] - [toucan.db :as db]) - (:import java.util.TimeZone)) + [toucan.db :as db])) (def ^:private public-endpoint "/auto/dashboard/") @@ -287,12 +284,13 @@ :type :type/DateTime ((juxt :earliest :latest)) - (map date/str->date-time))] - (condp > (t/in-hours (t/interval earliest latest)) - 3 :minute - (* 24 7) :hour - (* 24 30 6) :day - (* 24 30 12 10) :month + (map u.date/parse))] + ;; e.g. if 3 hours > [duration between earliest and latest] then use `:minute` resolution + (condp u.date/greater-than-period-duration? (u.date/period-duration earliest latest) + (t/hours 3) :minute + (t/days 7) :hour + (t/months 6) :day + (t/years 10) :month :year) :day)) @@ -1070,41 +1068,37 @@ (defn- pluralize [x] - (case (mod x 10) + ;; the `int` cast here is to fix performance warnings if `*warn-on-reflection*` is enabled + (case (int (mod x 10)) 1 (tru "{0}st" x) 2 (tru "{0}nd" x) 3 (tru "{0}rd" x) (tru "{0}th" x))) (defn- humanize-datetime - [dt unit] - (let [dt (date/str->date-time dt) - tz (.getID ^TimeZone @date/jvm-timezone) - unparse-with-formatter (fn [formatter dt] - (t.format/unparse - (t.format/formatter formatter (t/time-zone-for-id tz)) - dt))] + [t-str unit] + (let [dt (u.date/parse t-str)] (case unit - :second (tru "at {0}" (unparse-with-formatter "h:mm:ss a, MMMM d, YYYY" dt)) - :minute (tru "at {0}" (unparse-with-formatter "h:mm a, MMMM d, YYYY" dt)) - :hour (tru "at {0}" (unparse-with-formatter "h a, MMMM d, YYYY" dt)) - :day (tru "on {0}" (unparse-with-formatter "MMMM d, YYYY" dt)) + :second (tru "at {0}" (t/format "h:mm:ss a, MMMM d, YYYY" dt)) + :minute (tru "at {0}" (t/format "h:mm a, MMMM d, YYYY" dt)) + :hour (tru "at {0}" (t/format "h a, MMMM d, YYYY" dt)) + :day (tru "on {0}" (t/format "MMMM d, YYYY" dt)) :week (tru "in {0} week - {1}" - (pluralize (date/date-extract :week-of-year dt tz)) - (str (date/date-extract :year dt tz))) - :month (tru "in {0}" (unparse-with-formatter "MMMM YYYY" dt)) + (pluralize (u.date/extract dt :week-of-year)) + (str (u.date/extract dt :year))) + :month (tru "in {0}" (t/format "MMMM YYYY" dt)) :quarter (tru "in Q{0} - {1}" - (date/date-extract :quarter-of-year dt tz) - (str (date/date-extract :year dt tz))) - :year (unparse-with-formatter "YYYY" dt) - :day-of-week (unparse-with-formatter "EEEE" dt) - :hour-of-day (tru "at {0}" (unparse-with-formatter "h a" dt)) - :month-of-year (unparse-with-formatter "MMMM" dt) - :quarter-of-year (tru "Q{0}" (date/date-extract :quarter-of-year dt tz)) + (u.date/extract dt :quarter-of-year) + (str (u.date/extract dt :year))) + :year (t/format "YYYY" dt) + :day-of-week (t/format "EEEE" dt) + :hour-of-day (tru "at {0}" (t/format "h a" dt)) + :month-of-year (t/format "MMMM" dt) + :quarter-of-year (tru "Q{0}" (u.date/extract dt :quarter-of-year)) (:minute-of-hour :day-of-month :day-of-year - :week-of-year) (date/date-extract unit dt tz)))) + :week-of-year) (u.date/extract dt unit)))) (defn- field-reference->field [root field-reference] @@ -1136,7 +1130,7 @@ (->> field-reference (field-reference->field root) field-name)) ([{:keys [display_name unit] :as field}] (cond->> display_name - (some-> unit date/date-extract-units) (tru "{0} of {1}" (unit-name unit))))) + (some-> unit u.date/extract-units) (tru "{0} of {1}" (unit-name unit))))) (defmethod humanize-filter-value := [root [_ field-reference value]] diff --git a/src/metabase/automagic_dashboards/filters.clj b/src/metabase/automagic_dashboards/filters.clj index 71a954542ae..15263f9cb7b 100644 --- a/src/metabase/automagic_dashboards/filters.clj +++ b/src/metabase/automagic_dashboards/filters.clj @@ -5,7 +5,9 @@ [metabase.models.field :as field :refer [Field]] [metabase.query-processor.util :as qp.util] [metabase.util :as u] - [metabase.util.schema :as su] + [metabase.util + [date-2 :as u.date] + [schema :as su]] [schema.core :as s] [toucan.db :as db])) @@ -54,7 +56,7 @@ (defn datetime? "Does `field` represent a temporal value, i.e. a date, time, or datetime?" [field] - (and (not ((disj metabase.util.date/date-extract-units :year) (:unit field))) + (and (not ((disj u.date/extract-units :year) (:unit field))) (or (isa? (:base_type field) :type/Temporal) (field/unix-timestamp? field)))) diff --git a/src/metabase/cmd.clj b/src/metabase/cmd.clj index 34e87549cb4..b1c56553cfe 100644 --- a/src/metabase/cmd.clj +++ b/src/metabase/cmd.clj @@ -20,8 +20,7 @@ [config :as config] [db :as mdb] [util :as u]] - [metabase.plugins.classloader :as classloader] - [metabase.util.date :as du])) + [metabase.plugins.classloader :as classloader])) (defn ^:command migrate "Run database migrations. Valid options for `direction` are `up`, `force`, `down-one`, `print`, or `release-locks`." @@ -52,7 +51,7 @@ ;; override env var that would normally make Jetty block forever (classloader/require 'environ.core 'metabase.core) (alter-var-root #'environ.core/env assoc :mb-jetty-join "false") - (du/profile "start-normally" ((resolve 'metabase.core/start-normally)))) + (u/profile "start-normally" ((resolve 'metabase.core/start-normally)))) (defn ^:command reset-password "Reset the password for a user with `email-address`." diff --git a/src/metabase/cmd/dump_to_h2.clj b/src/metabase/cmd/dump_to_h2.clj index c4bbc801188..d330d63b30b 100644 --- a/src/metabase/cmd/dump_to_h2.clj +++ b/src/metabase/cmd/dump_to_h2.clj @@ -127,7 +127,7 @@ k))))] {:cols dest-keys :vals (for [row objs] - (map (comp u/jdbc-clob->str row) source-keys))})) + (map row source-keys))})) (def ^:private chunk-size 100) diff --git a/src/metabase/cmd/load_from_h2.clj b/src/metabase/cmd/load_from_h2.clj index fb84dda1d46..3bbbf85c810 100644 --- a/src/metabase/cmd/load_from_h2.clj +++ b/src/metabase/cmd/load_from_h2.clj @@ -134,7 +134,7 @@ k))))] {:cols dest-keys :vals (for [row objs] - (map (comp u/jdbc-clob->str row) source-keys))})) + (map row source-keys))})) (def ^:private chunk-size 100) diff --git a/src/metabase/db.clj b/src/metabase/db.clj index 3f74aa3b497..2a893729cc3 100644 --- a/src/metabase/db.clj +++ b/src/metabase/db.clj @@ -11,10 +11,11 @@ [config :as config] [connection-pool :as connection-pool] [util :as u]] - [metabase.db.spec :as dbspec] + [metabase.db + [jdbc-protocols :as db.jdbc-protocols] + [spec :as db.spec]] [metabase.plugins.classloader :as classloader] [metabase.util - [date :as du] [i18n :refer [trs]] [schema :as su]] [ring.util.codec :as codec] @@ -40,7 +41,7 @@ ;; MVStore engine anyway so this only affects people still with legacy PageStore databases ;; ;; Tell H2 to defrag when Metabase is shut down -- can reduce DB size by multiple GIGABYTES -- see #6510 - options ";DB_CLOSE_DELAY=-1;MVCC=TRUE;DEFRAG_ALWAYS=TRUE"] + options ";DB_CLOSE_DELAY=-1;MVCC=TRUE;DEFRAG_ALWAYS=TRUE"] ;; H2 wants file path to always be absolute (str "file:" (.getAbsolutePath (io/file db-file-name)) @@ -146,9 +147,9 @@ {:pre [(map? db-details)]} ;; TODO: it's probably a good idea to put some more validation here and be really strict about what's in `db-details` (case (:type db-details) - :h2 (dbspec/h2 db-details) - :mysql (dbspec/mysql (assoc db-details :db (:dbname db-details))) - :postgres (dbspec/postgres (assoc db-details :db (:dbname db-details)))))) + :h2 (db.spec/h2 db-details) + :mysql (db.spec/mysql (assoc db-details :db (:dbname db-details))) + :postgres (db.spec/postgres (assoc db-details :db (:dbname db-details)))))) ;;; +----------------------------------------------------------------------------------------------------------------+ @@ -371,7 +372,8 @@ :postgres :ansi :h2 :h2 :mysql :mysql)) - (db/set-default-db-connection! (new-connection-pool spec))) + (db/set-default-db-connection! (new-connection-pool spec)) + (db/set-default-jdbc-options! {:read-columns db.jdbc-protocols/read-columns})) ;;; +----------------------------------------------------------------------------------------------------------------+ @@ -474,12 +476,12 @@ (defn setup-db!* "Connects to db and runs migrations." [db-details auto-migrate] - (du/profile (trs "Database setup") - (u/with-us-locale - (verify-db-connection db-details) - (run-schema-migrations! auto-migrate db-details) - (create-connection-pool! (jdbc-details db-details)) - (run-data-migrations!))) + (u/profile (trs "Database setup") + (u/with-us-locale + (verify-db-connection db-details) + (run-schema-migrations! auto-migrate db-details) + (create-connection-pool! (jdbc-details db-details)) + (run-data-migrations!))) nil) (defn- setup-db-from-env!* [] diff --git a/src/metabase/db/jdbc_protocols.clj b/src/metabase/db/jdbc_protocols.clj new file mode 100644 index 00000000000..1ff4c4cfa40 --- /dev/null +++ b/src/metabase/db/jdbc_protocols.clj @@ -0,0 +1,157 @@ +(ns metabase.db.jdbc-protocols + "Implementations of `clojure.java.jdbc` protocols for the Metabase application database. These handle type mappings + for setting parameters and for reading results from the DB — mainly by automatically converting CLOBs to Strings and + using new `java.time` classes." + (:require [clojure.java.jdbc :as jdbc] + [clojure.string :as str] + [clojure.tools.logging :as log] + [java-time :as t] + [metabase.plugins.classloader :as classloader] + [metabase.util.date-2 :as u.date]) + (:import java.io.BufferedReader + [java.sql PreparedStatement ResultSet ResultSetMetaData Types] + [java.time Instant LocalDate LocalDateTime LocalTime OffsetDateTime OffsetTime ZonedDateTime])) + +(def ^:private db-type + (delay + (classloader/require 'metabase.db) + ((resolve 'metabase.db/db-type)))) + +(defn- set-object + [^PreparedStatement stmt ^Integer index object ^Integer target-sql-type] + (.setObject stmt index object target-sql-type)) + +(extend-protocol jdbc/ISQLParameter + ;; DB's don't seem to handle Instant correctly so convert it to an OffsetDateTime with zone offset = 0 + Instant + (set-parameter [t stmt i] + (jdbc/set-parameter (t/offset-date-time t (t/zone-offset 0)) stmt i)) + + LocalDate + (set-parameter [t stmt i] + (set-object stmt i t Types/DATE)) + + LocalDateTime + (set-parameter [t stmt i] + (set-object stmt i t Types/TIMESTAMP)) + + LocalTime + (set-parameter [t stmt i] + (set-object stmt i t Types/TIME)) + + OffsetDateTime + (set-parameter [t stmt i] + (if (= @db-type :mysql) + ;; Regardless of session timezone it seems to be the case that OffsetDateTimes get normalized to UTC inside MySQL + ;; + ;; Since MySQL TIMESTAMPs aren't timezone-aware this means comparisons are done between timestamps in the report + ;; timezone and the local datetime portion of the parameter, in UTC. Bad! + ;; + ;; Convert it to a LocalDateTime, in the report timezone, so comparisions will work correctly. + ;; + ;; See also — https://dev.mysql.com/doc/refman/5.5/en/datetime.html + (let [offset (.. (t/zone-id) getRules (getOffset (t/instant t))) + t (t/local-date-time (t/with-offset-same-instant t offset))] + (set-object stmt i t Types/TIMESTAMP)) + ;; h2 and Postgres work as expected + (set-object stmt i t Types/TIMESTAMP_WITH_TIMEZONE))) + + ;; MySQL, Postgres, and H2 all don't support OffsetTime + OffsetTime + (set-parameter [t stmt i] + (set-object stmt i (t/local-time (t/with-offset-same-instant t (t/zone-offset 0))) Types/TIME)) + + ;; Similarly, none of them handle ZonedDateTime out of the box either, so convert it to an OffsetDateTime first + ZonedDateTime + (set-parameter [t stmt i] + (jdbc/set-parameter (t/offset-date-time t) stmt i))) + +(extend-protocol jdbc/IResultSetReadColumn + org.postgresql.util.PGobject + (result-set-read-column [clob _ _] + (.getValue clob)) + + org.h2.jdbc.JdbcClob + (result-set-read-column [clob _ _] + (letfn [(clob->str [^BufferedReader buffered-reader] + (loop [acc []] + (if-let [line (.readLine buffered-reader)] + (recur (conj acc line)) + (str/join "\n" acc))))] + (with-open [reader (.getCharacterStream clob)] + (if (instance? BufferedReader reader) + (clob->str reader) + (with-open [buffered-reader (BufferedReader. reader)] + (clob->str buffered-reader))))))) + +(defmulti ^:private read-column + {:arglists '([rs rsmeta i])} + (fn [_ ^ResultSetMetaData rsmeta ^Integer i] + (.getColumnType rsmeta i))) + +(defmethod read-column :default + [^ResultSet rs _ ^Integer i] + (.getObject rs i)) + +(defmethod read-column Types/TIMESTAMP + [^ResultSet rs ^ResultSetMetaData rsmeta ^Integer i] + (case @db-type + :postgres + ;; for some reason postgres `TIMESTAMP WITH TIME ZONE` columns still come back as `Type/TIMESTAMP`, which seems + ;; like a bug with the JDBC driver? + (let [^Class klass (if (= (str/lower-case (.getColumnTypeName rsmeta i)) "timestamptz") + OffsetDateTime + LocalDateTime)] + (.getObject rs i klass)) + + :mysql + ;; MySQL TIMESTAMPS are actually TIMESTAMP WITH LOCAL TIME ZONE, i.e. they are stored normalized to UTC when stored. + ;; However, MySQL returns them in the report time zone in an effort to make our lives horrible. + ;; + ;; Check and see if the column type is `TIMESTAMP` (as opposed to `DATETIME`, which is the equivalent of + ;; LocalDateTime), and normalize it to a UTC timestamp if so. + (let [t (.getObject rs i LocalDateTime)] + (if (= (.getColumnTypeName rsmeta i) "TIMESTAMP") + (t/with-offset-same-instant (t/offset-date-time t (t/zone-id)) (t/zone-offset 0)) + t)) + + ;; h2 + (.getObject rs i LocalDateTime))) + +(defmethod read-column Types/TIMESTAMP_WITH_TIMEZONE + [^ResultSet rs _ ^Integer i] + (.getObject rs i OffsetDateTime)) + +(defmethod read-column Types/DATE + [^ResultSet rs _ ^Integer i] + (.getObject rs i LocalDate)) + +(defmethod read-column Types/TIME + [^ResultSet rs _ ^Integer i] + (case @db-type + :postgres + ;; Sometimes Postgres times come back as strings like `07:23:18.331+00` (no minute in offset) and there's a bug in + ;; the JDBC driver where it can't parse those correctly. We can do it ourselves in that case. + (try + (.getObject rs i LocalTime) + (catch Throwable _ + (let [s (.getString rs i)] + (log/tracef "Error in Postgres JDBC driver reading TIME value, fetching as string '%s'" s) + (u.date/parse s)))) + + ;; H2 & MySQL work as expected + (.getObject rs i LocalTime))) + +(defmethod read-column Types/TIME_WITH_TIMEZONE + [^ResultSet rs _ ^Integer i] + (.getObject rs i OffsetTime)) + +(defn read-columns + "Default `clojure.java.jdbc` `:read-columns` method to use for Metabase. Reads temporal values as `java.sql.time` + types rather than legacy `java.sql.Timestamp` and the like." + [rs rsmeta indexes] + (mapv + (fn [i] + (-> (read-column rs rsmeta i) + (jdbc/result-set-read-column rsmeta i))) + indexes)) diff --git a/src/metabase/db/migrations.clj b/src/metabase/db/migrations.clj index df11c579650..2d968912170 100644 --- a/src/metabase/db/migrations.clj +++ b/src/metabase/db/migrations.clj @@ -29,9 +29,7 @@ [pulse :refer [Pulse]] [setting :as setting :refer [Setting]] [user :refer [User]]] - [metabase.util - [date :as du] - [i18n :refer [trs]]] + [metabase.util.i18n :refer [trs]] [toucan [db :as db] [models :as models]]) @@ -53,7 +51,7 @@ (@migration-var) (db/insert! DataMigrations :id migration-name - :timestamp (du/new-sql-timestamp))))) + :timestamp :%now)))) (def ^:private data-migrations (atom [])) diff --git a/src/metabase/driver.clj b/src/metabase/driver.clj index f8ad5721aa4..affb6ebb235 100644 --- a/src/metabase/driver.clj +++ b/src/metabase/driver.clj @@ -12,7 +12,6 @@ [metabase.plugins.classloader :as classloader] [metabase.util :as u] [metabase.util - [date :as du] [i18n :refer [deferred-tru trs tru]] [schema :as su]] [schema.core :as s] @@ -135,7 +134,7 @@ [driver] (when-not *compile-files* (when-not (registered? driver) - (du/profile (trs "Load driver {0}" driver) + (u/profile (trs "Load driver {0}" driver) (require-driver-ns driver) ;; ok, hopefully it was registered now. If not, try again, but reload the entire driver namespace (when-not (registered? driver) diff --git a/src/metabase/driver/h2.clj b/src/metabase/driver/h2.clj index c6b8c252bde..b08da9b2fb1 100644 --- a/src/metabase/driver/h2.clj +++ b/src/metabase/driver/h2.clj @@ -17,9 +17,7 @@ [metabase.util [honeysql-extensions :as hx] [i18n :refer [deferred-tru tru]]]) - (:import java.sql.Time - java.time.OffsetTime - java.util.Date)) + (:import java.time.OffsetTime)) (driver/register! :h2, :parent :sql-jdbc) diff --git a/src/metabase/driver/postgres.clj b/src/metabase/driver/postgres.clj index f96773ca2a2..f1577df57e9 100644 --- a/src/metabase/driver/postgres.clj +++ b/src/metabase/driver/postgres.clj @@ -19,7 +19,6 @@ [metabase.driver.sql.query-processor :as sql.qp] [metabase.driver.sql.util.unprepare :as unprepare] [metabase.util - [date :as du] [date-2 :as u.date] [honeysql-extensions :as hx] [ssh :as ssh]]) @@ -163,7 +162,7 @@ (defmethod unprepare/unprepare-value [:postgres Date] [_ value] - (format "'%s'::timestamp" (du/date->iso-8601 value))) + (format "'%s'::timestamp" (u.date/format value))) (prefer-method unprepare/unprepare-value [:sql Time] [:postgres Date]) diff --git a/src/metabase/driver/sql/util/unprepare.clj b/src/metabase/driver/sql/util/unprepare.clj index 6a49a0f6cfc..ebb21d8c465 100644 --- a/src/metabase/driver/sql/util/unprepare.clj +++ b/src/metabase/driver/sql/util/unprepare.clj @@ -71,23 +71,6 @@ [driver t] (unprepare-value driver (t/offset-date-time t (t/zone-offset 0)))) -;; for legacy situtations -(defmethod unprepare-value [:sql java.sql.Time] - [driver t] - (unprepare-value driver (t/local-time t))) - -(defmethod unprepare-value [:sql java.sql.Date] - [driver t] - (unprepare-value driver (t/local-date t))) - -(defmethod unprepare-value [:sql java.sql.Timestamp] - [driver t] - (unprepare-value driver (t/local-date-time t))) - -(defmethod unprepare-value [:sql java.util.Date] - [driver t] - (unprepare-value driver (t/instant t))) - ;; TODO - I think a name like `deparameterize` would be more appropriate here (defmulti ^String unprepare diff --git a/src/metabase/email/messages.clj b/src/metabase/email/messages.clj index 2ebc057b163..928f37342b5 100644 --- a/src/metabase/email/messages.clj +++ b/src/metabase/email/messages.clj @@ -4,6 +4,7 @@ (:require [clojure.core.cache :as cache] [clojure.tools.logging :as log] [hiccup.core :refer [html]] + [java-time :as t] [medley.core :as m] [metabase [config :as config] @@ -15,7 +16,6 @@ [body :as render.body] [style :as render.style]] [metabase.util - [date :as du] [export :as export] [i18n :refer [deferred-trs trs tru]] [quotation :as quotation] @@ -71,7 +71,7 @@ :invitorEmail (:email invitor) :company company :joinUrl join-url - :today (du/format-date "MMM' 'dd,' 'yyyy") + :today (t/format "MMM' 'dd,' 'yyyy" (t/zoned-date-time)) :logoHeader true} (random-quote-context)))] (email/send-message! @@ -106,7 +106,7 @@ :joinedUserName (:first_name new-user) :joinedViaSSO google-auth? :joinedUserEmail (:email new-user) - :joinedDate (du/format-date "EEEE, MMMM d") ; e.g. "Wednesday, July 13". TODO - is this what we want? + :joinedDate (t/format "EEEE, MMMM d" (t/zoned-date-time)) ; e.g. "Wednesday, July 13". TODO - is this what we want? :adminEmail (first recipients) :joinedUserEditUrl (str (public-settings/site-url) "/admin/people")} (random-quote-context)))))) @@ -145,7 +145,7 @@ (assoc obj :url ((model-name->url-fn model) id))) (defn- build-dependencies - "Build a sequence of dependencies from a MODEL-NAME->DEPENDENCIES map, and add various information such as obj URLs." + "Build a sequence of dependencies from a `model-name->dependencies` map, and add various information such as obj URLs." [model-name->dependencies] (for [model-name (sort (keys model-name->dependencies)) :let [user-facing-name (if (= model-name "Card") @@ -345,34 +345,34 @@ (str "metabase/email/" template-name ".mustache")) ;; Paths to the templates for all of the alerts emails -(def ^:private new-alert-template (template-path "alert_new_confirmation")) -(def ^:private you-unsubscribed-template (template-path "alert_unsubscribed")) +(def ^:private new-alert-template (template-path "alert_new_confirmation")) +(def ^:private you-unsubscribed-template (template-path "alert_unsubscribed")) (def ^:private admin-unsubscribed-template (template-path "alert_admin_unsubscribed_you")) -(def ^:private added-template (template-path "alert_you_were_added")) -(def ^:private stopped-template (template-path "alert_stopped_working")) -(def ^:private deleted-template (template-path "alert_was_deleted")) +(def ^:private added-template (template-path "alert_you_were_added")) +(def ^:private stopped-template (template-path "alert_stopped_working")) +(def ^:private deleted-template (template-path "alert_was_deleted")) (defn send-new-alert-email! - "Send out the initial 'new alert' email to the `CREATOR` of the alert" + "Send out the initial 'new alert' email to the `creator` of the alert" [{:keys [creator] :as alert}] (send-email! creator "You set up an alert" new-alert-template (default-alert-context alert alert-condition-text))) (defn send-you-unsubscribed-alert-email! - "Send an email to `WHO-UNSUBSCRIBED` letting them know they've unsubscribed themselves from `ALERT`" + "Send an email to `who-unsubscribed` letting them know they've unsubscribed themselves from `alert`" [alert who-unsubscribed] (send-email! who-unsubscribed "You unsubscribed from an alert" you-unsubscribed-template (default-alert-context alert))) (defn send-admin-unsubscribed-alert-email! - "Send an email to `USER-ADDED` letting them know `ADMIN` has unsubscribed them from `ALERT`" + "Send an email to `user-added` letting them know `admin` has unsubscribed them from `alert`" [alert user-added {:keys [first_name last_name] :as admin}] (let [admin-name (format "%s %s" first_name last_name)] (send-email! user-added "You’ve been unsubscribed from an alert" admin-unsubscribed-template (assoc (default-alert-context alert) :adminName admin-name)))) (defn send-you-were-added-alert-email! - "Send an email to `USER-ADDED` letting them know `ADMIN-ADDER` has added them to `ALERT`" + "Send an email to `user-added` letting them know `admin-adder` has added them to `alert`" [alert user-added {:keys [first_name last_name] :as admin-adder}] (let [subject (format "%s %s added you to an alert" first_name last_name)] (send-email! user-added subject added-template (default-alert-context alert alert-condition-text)))) diff --git a/src/metabase/events/last_login.clj b/src/metabase/events/last_login.clj index 6bd0857eedf..e4c4e78bd92 100644 --- a/src/metabase/events/last_login.clj +++ b/src/metabase/events/last_login.clj @@ -3,7 +3,6 @@ [clojure.tools.logging :as log] [metabase.events :as events] [metabase.models.user :refer [User]] - [metabase.util.date :as du] [toucan.db :as db])) (def ^:const last-login-topics @@ -26,7 +25,7 @@ (when-let [{object :item} last-login-event] ;; just make a simple attempt to set the `:last_login` for the given user to now (when-let [user-id (:user_id object)] - (db/update! User user-id, :last_login (du/new-sql-timestamp)))) + (db/update! User user-id, :last_login :%now))) (catch Throwable e (log/warn (format "Failed to process sync-database event. %s" (:topic last-login-event)) e)))) diff --git a/src/metabase/metabot/instance.clj b/src/metabase/metabot/instance.clj index 57bf42fa836..ff31db347e7 100644 --- a/src/metabase/metabot/instance.clj +++ b/src/metabase/metabot/instance.clj @@ -21,15 +21,14 @@ MetaBot duties." (:require [clojure.tools.logging :as log] [honeysql.core :as hsql] + [java-time :as t] [metabase [config :refer [local-process-uuid]] [util :as u]] [metabase.models.setting :as setting :refer [defsetting]] - [metabase.util - [date :as du] - [i18n :refer [trs]]] + [metabase.util.i18n :refer [trs]] [toucan.db :as db]) - (:import java.sql.Timestamp)) + (:import java.time.temporal.Temporal)) (defsetting ^:private metabot-instance-uuid "UUID of the active MetaBot instance (the Metabase process currently handling MetaBot duties.)" @@ -52,7 +51,7 @@ "Fetch the current timestamp from the DB. Why do this from the DB? It's not safe to assume multiple instances have clocks exactly in sync; but since each instance is using the same application DB, we can use it as a cannonical source of truth." - ^Timestamp [] + ^Temporal [] (-> (db/query {:select [[(hsql/raw "current_timestamp") :current_timestamp]]}) first :current_timestamp)) @@ -68,10 +67,8 @@ `last-checkin` is one of the few Settings that isn't cached, this always requires a DB call.)" [] (when-let [last-checkin (metabot-instance-last-checkin)] - (u/prog1 (-> (- (.getTime (current-timestamp-from-db)) - (.getTime last-checkin)) - (/ 1000)) - (log/debug (u/format-color 'magenta (trs "Last MetaBot checkin was {0} ago." (du/format-seconds <>))))))) + (u/prog1 (.getSeconds (t/duration last-checkin (current-timestamp-from-db))) + (log/debug (u/format-color 'magenta (trs "Last MetaBot checkin was {0} ago." (u/format-seconds <>))))))) (def ^:private ^Integer recent-checkin-timeout-interval-seconds "Number of seconds since the last MetaBot checkin that we will consider the MetaBot job to be 'up for grabs', diff --git a/src/metabase/middleware/json.clj b/src/metabase/middleware/json.clj index 3d58f6a46b7..5e239ba9fe6 100644 --- a/src/metabase/middleware/json.clj +++ b/src/metabase/middleware/json.clj @@ -3,7 +3,6 @@ (:require [cheshire [core :as json] [generate :as json.generate]] - [metabase.util :as u] [metabase.util.date-2 :as u.date] [ring.middleware.json :as ring.json] [ring.util @@ -41,14 +40,6 @@ ;; * `java.sql.Date` (SQL Dates -- .toString returns YYYY-MM-DD) (json.generate/add-encoder Object json.generate/encode-str) -(defn- ^:deprecated encode-jdbc-clob [clob json-generator] - (write-string! json-generator (u/jdbc-clob->str clob))) - -;; TODO - we should add logic to convert CLOBs to strings when they come out of the database instead of doing it at -;; JSON serialization time. Once that's done we can remove this stuff -(json.generate/add-encoder org.h2.jdbc.JdbcClob encode-jdbc-clob) ; H2 -(json.generate/add-encoder org.postgresql.util.PGobject encode-jdbc-clob) ; Postgres - ;; Binary arrays ("[B") -- hex-encode their first four bytes, e.g. "0xC42360D7" (json.generate/add-encoder (Class/forName "[B") @@ -80,7 +71,7 @@ ;;; +----------------------------------------------------------------------------------------------------------------+ (defn- streamed-json-response - "Write `RESPONSE-SEQ` to a PipedOutputStream as JSON, returning the connected PipedInputStream" + "Write `response-seq` to a PipedOutputStream as JSON, returning the connected PipedInputStream" [response-seq opts] (rui/piped-input-stream (fn [^OutputStream output-stream] diff --git a/src/metabase/middleware/log.clj b/src/metabase/middleware/log.clj index 8aac8d1a31f..f3bd43892aa 100644 --- a/src/metabase/middleware/log.clj +++ b/src/metabase/middleware/log.clj @@ -9,9 +9,7 @@ [metabase.async.util :as async.u] [metabase.middleware.util :as middleware.u] [metabase.query-processor.middleware.async :as qp.middleware.async] - [metabase.util - [date :as du] - [i18n :refer [trs]]] + [metabase.util.i18n :refer [trs]] [toucan.db :as db]) (:import clojure.core.async.impl.channels.ManyToManyChannel org.eclipse.jetty.util.thread.QueuedThreadPool)) @@ -42,7 +40,7 @@ [{:keys [start-time call-count-fn] :or {start-time (System/nanoTime) call-count-fn (constantly -1)}}] - (let [elapsed-time (du/format-nanoseconds (- (System/nanoTime) start-time)) + (let [elapsed-time (u/format-nanoseconds (- (System/nanoTime) start-time)) db-calls (call-count-fn)] (format "%s (%d DB calls)" elapsed-time db-calls))) diff --git a/src/metabase/middleware/security.clj b/src/metabase/middleware/security.clj index 3aa2d77b211..7c3d73c2909 100644 --- a/src/metabase/middleware/security.clj +++ b/src/metabase/middleware/security.clj @@ -2,12 +2,11 @@ "Ring middleware for adding security-related headers to API responses." (:require [clojure.java.io :as io] [clojure.string :as str] + [java-time :as t] [metabase.config :as config] [metabase.middleware.util :as middleware.u] [metabase.models.setting :refer [defsetting]] - [metabase.util - [date :as du] - [i18n :as ui18n :refer [deferred-tru]]] + [metabase.util.i18n :as ui18n :refer [deferred-tru]] [ring.util.codec :refer [base64-encode]]) (:import java.security.MessageDigest)) @@ -37,7 +36,7 @@ [] {"Cache-Control" "max-age=0, no-cache, must-revalidate, proxy-revalidate" "Expires" "Tue, 03 Jul 2001 06:00:00 GMT" - "Last-Modified" (du/format-date :rfc822)}) + "Last-Modified" (t/format :rfc-1123-date-time (t/zoned-date-time))}) (defn- cache-far-future-headers "Headers that tell browsers to cache a static resource for a long time." diff --git a/src/metabase/middleware/session.clj b/src/metabase/middleware/session.clj index a2276e14363..2fbedbf5e6b 100644 --- a/src/metabase/middleware/session.clj +++ b/src/metabase/middleware/session.clj @@ -1,6 +1,7 @@ (ns metabase.middleware.session "Ring middleware related to session (binding current user and permissions)." (:require [clojure.string :as str] + [java-time :as t] [metabase [config :as config] [db :as mdb]] @@ -9,11 +10,12 @@ [metabase.models [session :refer [Session]] [user :as user :refer [User]]] + [metabase.util.date-2 :as u.date] [ring.util.response :as resp] [schema.core :as s] [toucan.db :as db]) - (:import java.util.UUID - org.joda.time.DateTime)) + (:import java.time.temporal.Temporal + java.util.UUID)) ;; How do authenticated API requests work? Metabase first looks for a cookie called `metabase.SESSION`. This is the ;; normal way of doing things; this cookie gets set automatically upon login. `metabase.SESSION` is an HttpOnly @@ -32,7 +34,7 @@ (def ^:private ^String metabase-session-header "x-metabase-session") (defn- clear-cookie [response cookie-name] - (resp/set-cookie response cookie-name nil {:expires (DateTime. 0), :path "/"})) + (resp/set-cookie response cookie-name nil {:expires "Thu, 1 Jan 1970 00:00:00 GMT", :path "/"})) (defn- wrap-body-if-needed "You can't add a cookie (by setting the `:cookies` key of a response) if the response is an unwrapped JSON response; @@ -123,24 +125,22 @@ (handler (wrap-session-id* request) respond raise))) (defn- session-with-id - "Fetch a session with SESSION-ID, and include the User ID and superuser status associated with it." + "Fetch a session with `session-id`, and include the User ID and superuser status associated with it." [session-id] (db/select-one [Session :created_at :user_id (db/qualify User :is_superuser)] (mdb/join [Session :user_id] [User :id]) (db/qualify User :is_active) true (db/qualify Session :id) session-id)) -(defn- session-age-ms [session] - (- (System/currentTimeMillis) (or (when-let [^java.util.Date created-at (:created_at session)] - (.getTime created-at)) - 0))) +(s/defn ^:private session-expired? + ([session] + (session-expired? session (config/config-int :max-session-age))) -(defn- session-age-minutes [session] - (quot (session-age-ms session) 60000)) - -(defn- session-expired? [session] - (> (session-age-minutes session) - (config/config-int :max-session-age))) + ([{created-at :created_at} :- {(s/optional-key :created_at) (s/maybe Temporal), s/Keyword s/Any} + max-age-minutes :- s/Int] + (or + (not created-at) + (u.date/older-than? created-at (t/minutes max-age-minutes))))) (defn- current-user-info-for-session "Return User ID and superuser status for Session with `session-id` if it is valid and not expired." diff --git a/src/metabase/models.clj b/src/metabase/models.clj new file mode 100644 index 00000000000..5969c7d28ba --- /dev/null +++ b/src/metabase/models.clj @@ -0,0 +1,106 @@ +(ns metabase.models + (:require [metabase.models + [activity :as activity] + [card :as card] + [card-favorite :as card-favorite] + [collection :as collection] + [collection-revision :as collection-revision] + [dashboard :as dashboard] + [dashboard-card :as dashboard-card] + [dashboard-card-series :as dashboard-card-series] + [dashboard-favorite :as dashboard-favorite] + [database :as database] + [dependency :as dependency] + [dimension :as dimension] + [field :as field] + [field-values :as field-values] + [metric :as metric] + [metric-important-field :as metric-important-field] + [permissions :as permissions] + [permissions-group :as permissions-group] + [permissions-group-membership :as permissions-group-membership] + [permissions-revision :as permissions-revision] + [pulse :as pulse] + [pulse-card :as pulse-card] + [pulse-channel :as pulse-channel] + [pulse-channel-recipient :as pulse-channel-recipient] + [query-cache :as query-cache] + [query-execution :as query-execution] + [revision :as revision] + [segment :as segment] + [session :as session] + [setting :as setting] + [table :as table] + [user :as user] + [view-log :as view-log]] + [potemkin :as p])) + +;; Fool the linter +(comment activity/keep-me + card/keep-me + card-favorite/keep-me + collection/keep-me + collection-revision/keep-me + dashboard/keep-me + dashboard-card/keep-me + dashboard-card-series/keep-me + dashboard-favorite/keep-me + database/keep-me + dependency/keep-me + dimension/keep-me + field/keep-me + field-values/keep-me + metric/keep-me + metric-important-field/keep-me + permissions/keep-me + permissions-group/keep-me + permissions-group-membership/keep-me + permissions-revision/keep-me + pulse/keep-me + pulse-card/keep-me + pulse-channel/keep-me + pulse-channel-recipient/keep-me + query-cache/keep-me + query-execution/keep-me + revision/keep-me + segment/keep-me + session/keep-me + setting/keep-me + table/keep-me + user/keep-me + view-log/keep-me) + +(p/import-vars + [activity Activity] + [card Card] + [card-favorite CardFavorite] + [collection Collection] + [collection-revision CollectionRevision] + [dashboard Dashboard] + [dashboard-card DashboardCard] + [dashboard-card-series DashboardCardSeries] + [dashboard-favorite DashboardFavorite] + [database Database] + [dependency Dependency] + [dimension Dimension] + [field Field] + [field-values FieldValues] + [metric Metric] + [metric-important-field MetricImportantField] + [permissions Permissions] + [permissions-group PermissionsGroup] + [permissions-group-membership PermissionsGroupMembership] + [permissions-revision PermissionsRevision] + [pulse Pulse] + [pulse-card PulseCard] + [pulse-channel PulseChannel] + [pulse-channel-recipient PulseChannelRecipient] + [query-cache QueryCache] + [query-execution QueryExecution] + [revision Revision] + [segment Segment] + [session Session] + [setting Setting] + [table Table] + [user User] + [view-log ViewLog]) diff --git a/src/metabase/models/activity.clj b/src/metabase/models/activity.clj index c88a0e8cb79..8159d28952b 100644 --- a/src/metabase/models/activity.clj +++ b/src/metabase/models/activity.clj @@ -10,7 +10,6 @@ [metric :refer [Metric]] [pulse :refer [Pulse]] [segment :refer [Segment]]] - [metabase.util.date :as du] [toucan [db :as db] [models :as models]])) @@ -52,7 +51,7 @@ (models/defmodel Activity :activity) (defn- pre-insert [activity] - (let [defaults {:timestamp (du/new-sql-timestamp) + (let [defaults {:timestamp :%now :details {}}] (merge defaults activity))) @@ -70,7 +69,6 @@ ;;; ------------------------------------------------------ Etc. ------------------------------------------------------ - ;; ## Persistence Functions ;; TODO - this is probably the exact wrong way to have written this functionality. diff --git a/src/metabase/models/card.clj b/src/metabase/models/card.clj index a501de24f33..99ab866a606 100644 --- a/src/metabase/models/card.clj +++ b/src/metabase/models/card.clj @@ -167,7 +167,6 @@ (merge models/IModelDefaults {:hydration-keys (constantly [:card]) :types (constantly {:dataset_query :metabase-query - :description :clob :display :keyword :embedding_params :json :query_type :keyword diff --git a/src/metabase/models/collection.clj b/src/metabase/models/collection.clj index 72a1356eda0..699d99bfc67 100644 --- a/src/metabase/models/collection.clj +++ b/src/metabase/models/collection.clj @@ -837,7 +837,6 @@ models/IModel (merge models/IModelDefaults {:hydration-keys (constantly [:collection]) - :types (constantly {:name :clob, :description :clob}) :pre-insert pre-insert :post-insert post-insert :pre-update pre-update diff --git a/src/metabase/models/collection_revision.clj b/src/metabase/models/collection_revision.clj index 8cf08c4a78d..c8ca745b3c4 100644 --- a/src/metabase/models/collection_revision.clj +++ b/src/metabase/models/collection_revision.clj @@ -1,8 +1,6 @@ (ns metabase.models.collection-revision (:require [metabase.util :as u] - [metabase.util - [date :as du] - [i18n :refer [tru]]] + [metabase.util.i18n :refer [tru]] [toucan [db :as db] [models :as models]])) @@ -10,14 +8,13 @@ (models/defmodel CollectionRevision :collection_revision) (defn- pre-insert [revision] - (assoc revision :created_at (du/new-sql-timestamp))) + (assoc revision :created_at :%now)) (u/strict-extend (class CollectionRevision) models/IModel (merge models/IModelDefaults {:types (constantly {:before :json - :after :json - :remark :clob}) + :after :json}) :pre-insert pre-insert :pre-update (fn [& _] (throw (Exception. (tru "You cannot update a CollectionRevision!"))))})) diff --git a/src/metabase/models/dashboard.clj b/src/metabase/models/dashboard.clj index 5be46568503..1cf324587f9 100644 --- a/src/metabase/models/dashboard.clj +++ b/src/metabase/models/dashboard.clj @@ -63,7 +63,7 @@ models/IModel (merge models/IModelDefaults {:properties (constantly {:timestamped? true}) - :types (constantly {:description :clob, :parameters :json, :embedding_params :json}) + :types (constantly {:parameters :json, :embedding_params :json}) :pre-delete pre-delete :pre-insert pre-insert :post-select public-settings/remove-public-uuid-if-public-sharing-is-disabled}) diff --git a/src/metabase/models/dependency.clj b/src/metabase/models/dependency.clj index 753beec5abb..019026abbfa 100644 --- a/src/metabase/models/dependency.clj +++ b/src/metabase/models/dependency.clj @@ -3,7 +3,6 @@ example, a Card might use a Segment; a Dependency object will be used to track this dependency so appropriate actions can take place or be prevented when something changes." (:require [clojure.set :as set] - [metabase.util.date :as du] [potemkin.types :as p.types] [toucan [db :as db] @@ -20,15 +19,8 @@ (dependencies Card 13 {}) -> {:Segment [25 134 344] :Table [18]}")) - -;;; # Dependency Entity - (models/defmodel Dependency :dependency) - -;;; ## Persistence Functions - - (defn retrieve-dependencies "Get the list of dependencies for a given object." [entity id] @@ -56,7 +48,7 @@ dependencies+ (set/difference dependencies-new dependencies-old) dependencies- (set/difference dependencies-old dependencies-new)] (when (seq dependencies+) - (let [vs (map #(merge % {:model entity-name, :model_id id, :created_at (du/new-sql-timestamp)}) dependencies+)] + (let [vs (map #(merge % {:model entity-name, :model_id id, :created_at :%now}) dependencies+)] (db/insert-many! Dependency vs))) (when (seq dependencies-) (doseq [{:keys [dependent_on_model dependent_on_id]} dependencies-] diff --git a/src/metabase/models/field.clj b/src/metabase/models/field.clj index 59eb4a16dfd..88bc80867fa 100644 --- a/src/metabase/models/field.clj +++ b/src/metabase/models/field.clj @@ -144,8 +144,6 @@ :types (constantly {:base_type :keyword :special_type :keyword :visibility_type :keyword - :description :clob - :database_type :clob :has_field_values :keyword :fingerprint :json-for-fingerprints :settings :json}) diff --git a/src/metabase/models/interface.clj b/src/metabase/models/interface.clj index 37cf2966f46..75b0eb92242 100644 --- a/src/metabase/models/interface.clj +++ b/src/metabase/models/interface.clj @@ -6,7 +6,6 @@ [metabase.util :as u] [metabase.util [cron :as cron-util] - [date :as du] [encryption :as encryption] [i18n :refer [trs tru]]] [potemkin.types :as p.types] @@ -33,15 +32,14 @@ obj (json/generate-string obj))) -(defn- json-out [obj keywordize-keys?] - (let [s (u/jdbc-clob->str obj)] - (if (string? s) - (try - (json/parse-string s keywordize-keys?) - (catch Throwable e - (log/error e (str (trs "Error parsing JSON"))) - s)) - obj))) +(defn- json-out [s keywordize-keys?] + (if (string? s) + (try + (json/parse-string s keywordize-keys?) + (catch Throwable e + (log/error e (str (trs "Error parsing JSON"))) + s)) + s)) (defn json-out-with-keywordization "Default out function for columns given a Toucan type `:json`. Parses serialized JSON string and keywordizes keys." @@ -110,10 +108,6 @@ :in json-in :out #(some-> % json-out-with-keywordization set)) -(models/add-type! :clob - :in identity - :out u/jdbc-clob->str) - (def ^:private encrypted-json-in (comp encryption/maybe-encrypt json-in)) (def ^:private encrypted-json-out (comp json-out-with-keywordization encryption/maybe-decrypt)) @@ -123,11 +117,11 @@ (models/add-type! :encrypted-json :in encrypted-json-in - :out (comp cached-encrypted-json-out u/jdbc-clob->str)) + :out cached-encrypted-json-out) (models/add-type! :encrypted-text :in encryption/maybe-encrypt - :out (comp encryption/maybe-decrypt u/jdbc-clob->str)) + :out encryption/maybe-decrypt) (defn decompress "Decompress `compressed-bytes`." @@ -156,16 +150,15 @@ ;; handles those cases. (models/add-type! :keyword :in u/qualified-name - :out (comp keyword u/jdbc-clob->str)) - + :out keyword) ;;; properties (defn- add-created-at-timestamp [obj & _] - (assoc obj :created_at (du/new-sql-timestamp))) + (assoc obj :created_at :%now)) (defn- add-updated-at-timestamp [obj & _] - (assoc obj :updated_at (du/new-sql-timestamp))) + (assoc obj :updated_at :%now)) (models/add-property! :timestamped? :insert (comp add-created-at-timestamp add-updated-at-timestamp) diff --git a/src/metabase/models/metric.clj b/src/metabase/models/metric.clj index da515f56ae6..5d93a2beb25 100644 --- a/src/metabase/models/metric.clj +++ b/src/metabase/models/metric.clj @@ -39,7 +39,7 @@ models/IModel (merge models/IModelDefaults - {:types (constantly {:definition :metric-segment-definition, :description :clob}) + {:types (constantly {:definition :metric-segment-definition}) :properties (constantly {:timestamped? true}) :pre-delete pre-delete :pre-update pre-update}) diff --git a/src/metabase/models/metric_important_field.clj b/src/metabase/models/metric_important_field.clj index cf63488f7c3..fa47ad3102c 100644 --- a/src/metabase/models/metric_important_field.clj +++ b/src/metabase/models/metric_important_field.clj @@ -9,7 +9,7 @@ (u/strict-extend (class MetricImportantField) models/IModel (merge models/IModelDefaults - {:types (constantly {:definition :json, :description :clob})}) + {:types (constantly {:definition :json})}) i/IObjectPermissions (merge i/IObjectPermissionsDefaults {:can-read? (constantly true) diff --git a/src/metabase/models/permissions_revision.clj b/src/metabase/models/permissions_revision.clj index 1530e9ae8f6..131147d9328 100644 --- a/src/metabase/models/permissions_revision.clj +++ b/src/metabase/models/permissions_revision.clj @@ -1,8 +1,6 @@ (ns metabase.models.permissions-revision (:require [metabase.util :as u] - [metabase.util - [date :as du] - [i18n :refer [tru]]] + [metabase.util.i18n :refer [tru]] [toucan [db :as db] [models :as models]])) @@ -10,14 +8,13 @@ (models/defmodel PermissionsRevision :permissions_revision) (defn- pre-insert [revision] - (assoc revision :created_at (du/new-sql-timestamp))) + (assoc revision :created_at :%now)) (u/strict-extend (class PermissionsRevision) models/IModel (merge models/IModelDefaults {:types (constantly {:before :json - :after :json - :remark :clob}) + :after :json}) :pre-insert pre-insert :pre-update (fn [& _] (throw (Exception. (tru "You cannot update a PermissionsRevision!"))))})) diff --git a/src/metabase/models/query_execution.clj b/src/metabase/models/query_execution.clj index 2158c15f65b..3de2a2e3f37 100644 --- a/src/metabase/models/query_execution.clj +++ b/src/metabase/models/query_execution.clj @@ -23,7 +23,7 @@ (u/strict-extend (class QueryExecution) models/IModel (merge models/IModelDefaults - {:types (constantly {:json_query :json, :status :keyword, :context :keyword, :error :clob}) + {:types (constantly {:json_query :json, :status :keyword, :context :keyword}) :pre-insert pre-insert :pre-update (fn [& _] (throw (Exception. (tru "You cannot update a QueryExecution!")))) :post-select post-select})) diff --git a/src/metabase/models/revision.clj b/src/metabase/models/revision.clj index 7009e0917e2..602639b48c3 100644 --- a/src/metabase/models/revision.clj +++ b/src/metabase/models/revision.clj @@ -3,9 +3,7 @@ [metabase.models.revision.diff :refer [diff-string]] [metabase.models.user :refer [User]] [metabase.util :as u] - [metabase.util - [date :as du] - [i18n :refer [tru]]] + [metabase.util.i18n :refer [tru]] [potemkin.types :as p.types] [toucan [db :as db] @@ -13,7 +11,8 @@ [models :as models]])) (def ^:const max-revisions - "Maximum number of revisions to keep for each individual object. After this limit is surpassed, the oldest revisions will be deleted." + "Maximum number of revisions to keep for each individual object. After this limit is surpassed, the oldest revisions + will be deleted." 15) ;;; # IRevisioned Protocl @@ -22,13 +21,13 @@ "Methods an entity may optionally implement to control how revisions of an instance are saved and reverted to. All of these methods except for `serialize-instance` have a default implementation in `IRevisionedDefaults`." (serialize-instance [this id instance] - "Prepare an instance for serialization in a `Revision`.") + "Prepare an instance for serialization in a Revision.") (revert-to-revision! [this id user-id serialized-instance] - "Return an object to the state recorded by SERIALIZED-INSTANCE.") - (diff-map [this object1 object2] - "Return a map describing the difference between OBJECT1 and OBJECT2.") - (diff-str [this object1 object2] - "Return a string describing the difference between OBJECT1 and OBJECT2.")) + "Return an object to the state recorded by `serialized-INSTANCE`.") + (diff-map [this object-1 object-2] + "Return a map describing the difference between `object-1` and `object-2`.") + (diff-str [this object-1 object-2] + "Return a string describing the difference between `object-1` and `object-2`.")) ;;; # Reusable Base Implementations for IRevisioned functions @@ -66,7 +65,7 @@ (models/defmodel Revision :revision) (defn- pre-insert [revision] - (assoc revision :timestamp (du/new-sql-timestamp))) + (assoc revision :timestamp :%now)) (defn- do-post-select-for-object "Call the appropriate `post-select` methods (including the type functions) on the `:object` this Revision recorded. @@ -82,7 +81,7 @@ (u/strict-extend (class Revision) models/IModel (merge models/IModelDefaults - {:types (constantly {:object :json, :message :clob}) + {:types (constantly {:object :json}) :pre-insert pre-insert :pre-update (fn [& _] (throw (Exception. (tru "You cannot update a Revision!")))) :post-select do-post-select-for-object})) @@ -98,18 +97,18 @@ :description (diff-str entity (:object prev-revision) (:object revision))) ;; add revision user details (hydrate :user) - (update :user (u/rpartial select-keys [:id :first_name :last_name :common_name])) + (update :user select-keys [:id :first_name :last_name :common_name]) ;; Filter out irrelevant info (dissoc :model :model_id :user_id :object))) (defn revisions - "Get the revisions for ENTITY with ID in reverse chronological order." + "Get the revisions for `entity` with `id` in reverse chronological order." [entity id] {:pre [(models/model? entity) (integer? id)]} (db/select Revision, :model (:name entity), :model_id id, {:order-by [[:id :desc]]})) (defn revisions+details - "Fetch `revisions` for ENTITY with ID and add details." + "Fetch `revisions` for `entity` with `id` and add details." [entity id] (when-let [revisions (revisions entity id)] (loop [acc [], [r1 r2 & more] revisions] @@ -119,7 +118,7 @@ (conj more r2)))))) (defn- delete-old-revisions! - "Delete old revisions of ENTITY with ID when there are more than `max-revisions` in the DB." + "Delete old revisions of `entity` with `id` when there are more than `max-revisions` in the DB." [entity id] {:pre [(models/model? entity) (integer? id)]} (when-let [old-revisions (seq (drop max-revisions (map :id (db/select [Revision :id] @@ -129,8 +128,7 @@ (db/delete! Revision :id [:in old-revisions]))) (defn push-revision! - "Record a new `Revision` for ENTITY with ID. - Returns OBJECT." + "Record a new Revision for `entity` with `id`. Returns `object`." {:arglists '([& {:keys [object entity id user-id is-creation? message]}]), :style/indent 0} [& {object :object, :keys [entity id user-id is-creation? message], @@ -157,7 +155,7 @@ object) (defn revert! - "Revert ENTITY with ID to a given `Revision`." + "Revert `entity` with `id` to a given Revision." {:style/indent 0} [& {:keys [entity id user-id revision-id]}] {:pre [(models/model? entity) diff --git a/src/metabase/models/segment.clj b/src/metabase/models/segment.clj index ccd48a8ec29..03a62a81a50 100644 --- a/src/metabase/models/segment.clj +++ b/src/metabase/models/segment.clj @@ -33,7 +33,7 @@ models/IModel (merge models/IModelDefaults - {:types (constantly {:definition :metric-segment-definition, :description :clob}) + {:types (constantly {:definition :metric-segment-definition}) :properties (constantly {:timestamped? true}) :hydration-keys (constantly [:segment]) :pre-update pre-update}) diff --git a/src/metabase/models/session.clj b/src/metabase/models/session.clj index 285218a277d..99ab2611cb9 100644 --- a/src/metabase/models/session.clj +++ b/src/metabase/models/session.clj @@ -1,6 +1,5 @@ (ns metabase.models.session (:require [metabase.util :as u] - [metabase.util.date :as du] [toucan [db :as db] [models :as models]])) @@ -8,7 +7,7 @@ (models/defmodel Session :core_session) (defn- pre-insert [session] - (assoc session :created_at (du/new-sql-timestamp))) + (assoc session :created_at :%now)) (u/strict-extend (class Session) models/IModel diff --git a/src/metabase/models/setting.clj b/src/metabase/models/setting.clj index 7e94e65027b..c3d5e70ed1a 100644 --- a/src/metabase/models/setting.clj +++ b/src/metabase/models/setting.clj @@ -43,7 +43,7 @@ [util :as u]] [metabase.models.setting.cache :as cache] [metabase.util - [date :as du] + [date-2 :as u.date] [i18n :as ui18n :refer [deferred-trs deferred-tru trs tru]]] [schema.core :as s] [toucan @@ -71,7 +71,7 @@ :boolean Boolean :integer Long :double Double - :timestamp java.sql.Timestamp}) + :timestamp java.time.temporal.Temporal}) (def ^:private SettingDefinition {:name s/Keyword @@ -204,7 +204,7 @@ (defn get-timestamp "Get the string value of `setting-or-name` and parse it as an ISO-8601-formatted string, returning a Timestamp." [setting-or-name] - (du/->Timestamp (get-string setting-or-name) :no-timezone)) + (u.date/parse (get-string setting-or-name))) (defn get-csv "Get the string value of `setting-or-name` and parse it as CSV, returning a sequence of exploded strings." @@ -341,7 +341,7 @@ (defn set-timestamp! "Serialize `new-value` for `setting-or-name` as a ISO 8601-encoded timestamp string and save it." [setting-or-name new-value] - (set-string! setting-or-name (some-> new-value du/date->iso-8601))) + (set-string! setting-or-name (some-> new-value u.date/format))) (defn- serialize-csv [value] (cond diff --git a/src/metabase/models/table.clj b/src/metabase/models/table.clj index bdf58308d2b..835cd583737 100644 --- a/src/metabase/models/table.clj +++ b/src/metabase/models/table.clj @@ -50,11 +50,8 @@ models/IModel (merge models/IModelDefaults {:hydration-keys (constantly [:table]) - :types (constantly {:entity_type :keyword, - :visibility_type :keyword, - :description :clob, - :has_field_values :clob, - :fields_hash :clob}) + :types (constantly {:entity_type :keyword + :visibility_type :keyword}) :properties (constantly {:timestamped? true}) :pre-insert pre-insert :pre-delete pre-delete}) diff --git a/src/metabase/models/task_history.clj b/src/metabase/models/task_history.clj index 0e058eccb49..31ba1973872 100644 --- a/src/metabase/models/task_history.clj +++ b/src/metabase/models/task_history.clj @@ -1,9 +1,9 @@ (ns metabase.models.task-history (:require [clojure.tools.logging :as log] + [java-time :as t] [metabase.models.interface :as i] [metabase.util :as u] [metabase.util - [date :as du] [i18n :refer [trs]] [schema :as su]] [schema.core :as s] @@ -63,8 +63,8 @@ (try (db/insert! TaskHistory (assoc info - :started_at (du/->Timestamp start-time-ms) - :ended_at (du/->Timestamp end-time-ms) + :started_at (t/instant start-time-ms) + :ended_at (t/instant end-time-ms) :duration duration-ms)) (catch Throwable e (log/warn e (trs "Error saving task history")))))) diff --git a/src/metabase/models/user.clj b/src/metabase/models/user.clj index e3f351c2bd9..3ce41744967 100644 --- a/src/metabase/models/user.clj +++ b/src/metabase/models/user.clj @@ -14,7 +14,6 @@ [permissions-group :as group] [permissions-group-membership :as perm-membership :refer [PermissionsGroupMembership]]] [metabase.util - [date :as du] [i18n :refer [trs]] [schema :as su]] [schema.core :as s] @@ -35,7 +34,7 @@ (assert (not (:password_salt user)) "Don't try to pass an encrypted password to (insert! User). Password encryption is handled by pre-insert.") (let [salt (str (UUID/randomUUID)) - defaults {:date_joined (du/new-sql-timestamp) + defaults {:date_joined :%now :last_login nil :is_active true :is_superuser false}] diff --git a/src/metabase/models/view_log.clj b/src/metabase/models/view_log.clj index ec7a07eed8a..bc183698b42 100644 --- a/src/metabase/models/view_log.clj +++ b/src/metabase/models/view_log.clj @@ -2,13 +2,12 @@ "The ViewLog is used to log an event where a given User views a given object such as a Table or Card (Question)." (:require [metabase.models.interface :as i] [metabase.util :as u] - [metabase.util.date :as du] [toucan.models :as models])) (models/defmodel ViewLog :view_log) (defn- pre-insert [log-entry] - (let [defaults {:timestamp (du/new-sql-timestamp)}] + (let [defaults {:timestamp :%now}] (merge defaults log-entry))) (u/strict-extend (class ViewLog) diff --git a/src/metabase/plugins.clj b/src/metabase/plugins.clj index c8c4853ec8d..b3a1e110e3e 100644 --- a/src/metabase/plugins.clj +++ b/src/metabase/plugins.clj @@ -6,7 +6,6 @@ [metabase.plugins [classloader :as classloader] [initialize :as initialize]] - [metabase.util :as u] [metabase.util [files :as files] [i18n :refer [trs]]] @@ -20,28 +19,30 @@ ;; logic for determining plugins dir -- see below (defonce ^:private plugins-dir* (delay - (let [filename (plugins-dir-filename)] - (try - ;; attempt to create <current-dir>/plugins if it doesn't already exist. Check that the directory is readable. - (u/prog1 (files/get-path filename) - (files/create-dir-if-not-exists! <>) - (assert (Files/isWritable <>) - (trs "Metabase does not have permissions to write to plugins directory {0}" filename))) - ;; If we couldn't create the directory, or the directory is not writable, fall back to a temporary directory - ;; rather than failing to launch entirely. Log instructions for what should be done to fix the problem. - (catch Throwable e - (log/warn - e - (trs "Metabase cannot use the plugins directory {0}" filename) - "\n" - (trs "Please make sure the directory exists and that Metabase has permission to write to it.") - (trs "You can change the directory Metabase uses for modules by setting the environment variable MB_PLUGINS_DIR.") - (trs "Falling back to a temporary directory for now.")) - ;; Check whether the fallback temporary directory is writable. If it's not, there's no way for us to - ;; gracefully proceed here. Throw an Exception detailing the critical issues. - (u/prog1 (files/get-path (System/getProperty "java.io.tmpdir")) - (assert (Files/isWritable <>) - (trs "Metabase cannot write to temporary directory. Please set MB_PLUGINS_DIR to a writable directory and restart Metabase.")))))))) + (let [filename (plugins-dir-filename)] + (try + ;; attempt to create <current-dir>/plugins if it doesn't already exist. Check that the directory is readable. + (let [path (files/get-path filename)] + (files/create-dir-if-not-exists! path) + (assert (Files/isWritable path) + (trs "Metabase does not have permissions to write to plugins directory {0}" filename)) + path) + ;; If we couldn't create the directory, or the directory is not writable, fall back to a temporary directory + ;; rather than failing to launch entirely. Log instructions for what should be done to fix the problem. + (catch Throwable e + (log/warn + e + (trs "Metabase cannot use the plugins directory {0}" filename) + "\n" + (trs "Please make sure the directory exists and that Metabase has permission to write to it.") + (trs "You can change the directory Metabase uses for modules by setting the environment variable MB_PLUGINS_DIR.") + (trs "Falling back to a temporary directory for now.")) + ;; Check whether the fallback temporary directory is writable. If it's not, there's no way for us to + ;; gracefully proceed here. Throw an Exception detailing the critical issues. + (let [path (files/get-path (System/getProperty "java.io.tmpdir"))] + (assert (Files/isWritable path) + (trs "Metabase cannot write to temporary directory. Please set MB_PLUGINS_DIR to a writable directory and restart Metabase.")) + path)))))) ;; Actual logic is wrapped in a delay rather than a normal function so we don't log the error messages more than once ;; in cases where we have to fall back to the system temporary directory @@ -100,8 +101,7 @@ ;; if different JARs with `metabase` packages have different signing keys. Go ahead and ;; ignore it but let people know they can get rid of it. (log/warn - (u/format-color 'red - (trs "spark-deps.jar is no longer needed by Metabase 0.32.0+. You can delete it from the plugins directory.")))))] + (trs "spark-deps.jar is no longer needed by Metabase 0.32.0+. You can delete it from the plugins directory."))))] path)) (defn- has-manifest? ^Boolean [^Path path] @@ -117,10 +117,10 @@ (try (init-plugin! path) (catch Throwable e - (log/error e (u/format-color 'red (trs "Failied to initialize plugin {0}" (.getFileName path)))))))) + (log/error e (trs "Failied to initialize plugin {0}" (.getFileName path))))))) (defn- load! [] - (log/info (u/format-color 'magenta (trs "Loading plugins in {0}..." (str (plugins-dir))))) + (log/info (trs "Loading plugins in {0}..." (str (plugins-dir)))) (extract-system-modules!) (let [paths (plugins-paths)] (init-plugins! paths))) diff --git a/src/metabase/plugins/dependencies.clj b/src/metabase/plugins/dependencies.clj index 7d34cb2fbf7..05c930e9ff5 100644 --- a/src/metabase/plugins/dependencies.clj +++ b/src/metabase/plugins/dependencies.clj @@ -7,7 +7,6 @@ (def ^:private plugins-with-unsatisfied-deps (atom #{})) - (defn- dependency-type [{classname :class, plugin :plugin}] (cond classname :class diff --git a/src/metabase/plugins/lazy_loaded_driver.clj b/src/metabase/plugins/lazy_loaded_driver.clj index f7fc0e80a71..28a546df2a4 100644 --- a/src/metabase/plugins/lazy_loaded_driver.clj +++ b/src/metabase/plugins/lazy_loaded_driver.clj @@ -13,7 +13,6 @@ [metabase.driver.common :as driver.common] [metabase.plugins.init-steps :as init-steps] [metabase.util - [date :as du] [i18n :refer [trs]] [ssh :as ssh]]) (:import clojure.lang.MultiFn)) @@ -55,7 +54,7 @@ ;; implementation (remove-method driver/initialize! driver) ;; ok, do the init steps listed in the plugin mainfest - (du/profile (u/format-color 'magenta (trs "Load lazy loading driver {0}" driver)) + (u/profile (u/format-color 'magenta (trs "Load lazy loading driver {0}" driver)) (init-steps/do-init-steps! init-steps)) ;; ok, now go ahead and call `driver/initialize!` a second time on the driver in case it actually has ;; an implementation of `initialize!` other than this one. If it does not, we'll just end up hitting diff --git a/src/metabase/public_settings.clj b/src/metabase/public_settings.clj index 1a0c158dd20..3e06b805fe7 100644 --- a/src/metabase/public_settings.clj +++ b/src/metabase/public_settings.clj @@ -1,6 +1,7 @@ (ns metabase.public-settings (:require [clojure.string :as str] [clojure.tools.logging :as log] + [java-time :as t] [metabase [config :as config] [types :as types] @@ -15,7 +16,7 @@ [i18n :refer [available-locales-with-names deferred-tru set-locale trs tru]] [password :as password]] [toucan.db :as db]) - (:import [java.util TimeZone UUID])) + (:import java.util.UUID)) (defsetting check-for-updates (deferred-tru "Identify when new versions of Metabase are available.") @@ -185,16 +186,14 @@ (assoc object :public_uuid nil) object)) - -(defn- short-timezone-name* - "Get a short display name (e.g. `PST`) for `report-timezone`, or fall back to the System default if it's not set." - [^String timezone-name] - (let [^TimeZone timezone (or (when (seq timezone-name) - (TimeZone/getTimeZone timezone-name)) - (TimeZone/getDefault))] - (.getDisplayName timezone (.inDaylightTime timezone (java.util.Date.)) TimeZone/SHORT))) - -(def ^:private short-timezone-name (memoize short-timezone-name*)) +(defn- short-timezone-name [timezone-id] + (let [^java.time.ZoneId zone (if (seq timezone-id) + (t/zone-id timezone-id) + (t/zone-id))] + (.getDisplayName + zone + java.time.format.TextStyle/SHORT + (java.util.Locale/getDefault)))) (defn- resolve-setting [ns-symb setting-symb] (classloader/require ns-symb) diff --git a/src/metabase/pulse.clj b/src/metabase/pulse.clj index 062fdb13aa0..88e070542f1 100644 --- a/src/metabase/pulse.clj +++ b/src/metabase/pulse.clj @@ -5,22 +5,22 @@ [email :as email] [query-processor :as qp] [util :as u]] - [metabase.driver.util :as driver.u] [metabase.email.messages :as messages] [metabase.integrations.slack :as slack] [metabase.middleware.session :as session] [metabase.models [card :refer [Card]] + [database :refer [Database]] [pulse :as pulse :refer [Pulse]]] [metabase.pulse.render :as render] + [metabase.query-processor.timezone :as qp.timezone] [metabase.util [i18n :refer [deferred-tru trs tru]] [ui-logic :as ui] [urls :as urls]] [schema.core :as s] [toucan.db :as db]) - (:import java.util.TimeZone - metabase.models.card.CardInstance)) + (:import metabase.models.card.CardInstance)) ;;; ------------------------------------------------- PULSE SENDING -------------------------------------------------- @@ -48,12 +48,11 @@ (or (:database_id card) (get-in card [:dataset_query :database]))) -(s/defn defaulted-timezone :- TimeZone - "Returns the timezone for the given `card`. Either the report timezone (if applicable) or the JVM timezone." +(s/defn defaulted-timezone :- s/Str + "Returns the timezone ID for the given `card`. Either the report timezone (if applicable) or the JVM timezone." [card :- CardInstance] - (let [^String timezone-str (or (some-> card database-id driver.u/database->driver driver.u/report-timezone-if-supported) - (System/getProperty "user.timezone"))] - (TimeZone/getTimeZone timezone-str))) + (or (some-> card database-id Database qp.timezone/results-timezone-id) + (qp.timezone/system-timezone-id))) (defn- first-question-name [pulse] (-> pulse :cards first :name)) diff --git a/src/metabase/pulse/render.clj b/src/metabase/pulse/render.clj index 6824010c1d8..e8acf9f2428 100644 --- a/src/metabase/pulse/render.clj +++ b/src/metabase/pulse/render.clj @@ -91,7 +91,7 @@ (:include_xls card))) (s/defn ^:private render-pulse-card-body :- common/RenderedPulseCard - [render-type timezone card {:keys [data error], :as results}] + [render-type timezone-id :- (s/maybe s/Str) card {:keys [data error], :as results}] (try (when error (let [^String msg (tru "Card has errors: {0}" error)] @@ -100,7 +100,7 @@ (when (is-attached? card) :attached) :unknown)] - (body/render chart-type render-type timezone card data)) + (body/render chart-type render-type timezone-id card data)) (catch Throwable e (log/error e (trs "Pulse card render error")) (body/render :error nil nil nil nil)))) @@ -111,9 +111,9 @@ (s/defn ^:private render-pulse-card :- common/RenderedPulseCard "Render a single `card` for a `Pulse` to Hiccup HTML. `result` is the QP results." - [render-type timezone card results] + [render-type timezone-id :- (s/maybe s/Str) card results] (let [{title :content title-attachments :attachments} (make-title-if-needed render-type card) - {pulse-body :content body-attachments :attachments} (render-pulse-card-body render-type timezone card results)] + {pulse-body :content body-attachments :attachments} (render-pulse-card-body render-type timezone-id card results)] {:attachments (merge title-attachments body-attachments) :content [:a {:href (card-href card) :target "_blank" @@ -129,14 +129,14 @@ (defn render-pulse-card-for-display "Same as `render-pulse-card` but isn't intended for an email, rather for previewing so there is no need for attachments" - [timezone card results] - (:content (render-pulse-card :inline timezone card results))) + [timezone-id card results] + (:content (render-pulse-card :inline timezone-id card results))) (s/defn render-pulse-section :- common/RenderedPulseCard "Render a specific section of a Pulse, i.e. a single Card, to Hiccup HTML." - [timezone {card :card {:keys [data] :as result} :result}] + [timezone-id {card :card {:keys [data] :as result} :result}] (let [{:keys [attachments content]} (binding [*include-title* true] - (render-pulse-card :attachment timezone card result))] + (render-pulse-card :attachment timezone-id card result))] {:attachments attachments :content [:div {:style (style/style (merge {:margin-top :10px @@ -151,7 +151,7 @@ :box-shadow "0 1px 2px rgba(0, 0, 0, .08)"})))} content]})) -(defn render-pulse-card-to-png +(s/defn render-pulse-card-to-png :- bytes "Render a `pulse-card` as a PNG. `data` is the `:data` from a QP result (I think...)" - ^bytes [timezone pulse-card result] - (png/render-html-to-png (render-pulse-card :inline timezone pulse-card result) card-width)) + [timezone-id :- (s/maybe s/Str) pulse-card result] + (png/render-html-to-png (render-pulse-card :inline timezone-id pulse-card result) card-width)) diff --git a/src/metabase/pulse/render/body.clj b/src/metabase/pulse/render/body.clj index 3821c4eb8c4..a1b625d2564 100644 --- a/src/metabase/pulse/render/body.clj +++ b/src/metabase/pulse/render/body.clj @@ -40,10 +40,10 @@ ;;; --------------------------------------------------- Formatting --------------------------------------------------- -(defn- format-cell - [timezone value col] +(s/defn ^:private format-cell + [timezone-id :- (s/maybe s/Str) value col] (cond - (types/temporal-field? col) (datetime/format-timestamp timezone value col) + (types/temporal-field? col) (datetime/format-temporal-str timezone-id value col) (and (number? value) (not (types/temporal-field? col))) (common/format-number value) :else (str value))) @@ -80,9 +80,9 @@ column-name)) :bar-width (when include-bar? 99)}) -(defn- query-results->row-seq +(s/defn ^:private query-results->row-seq "Returns a seq of stringified formatted rows that can be rendered into HTML" - [timezone remapping-lookup cols rows bar-column max-value] + [timezone-id :- (s/maybe s/Str) remapping-lookup cols rows bar-column max-value] (for [row rows] {:bar-width (when-let [bar-value (and bar-column (bar-column row))] ;; cast to double to avoid "Non-terminating decimal expansion" errors @@ -94,17 +94,17 @@ [(nth cols (get remapping-lookup (:name maybe-remapped-col))) (nth row (get remapping-lookup (:name maybe-remapped-col)))] [maybe-remapped-col maybe-remapped-row-cell])]] - (format-cell timezone row-cell col))})) + (format-cell timezone-id row-cell col))})) -(defn- prep-for-html-rendering +(s/defn ^:private prep-for-html-rendering "Convert the query results (`cols` and `rows`) into a formatted seq of rows (list of strings) that can be rendered as HTML" - [timezone cols rows bar-column max-value column-limit] + [timezone-id :- (s/maybe s/Str) cols rows bar-column max-value column-limit] (let [remapping-lookup (create-remapping-lookup cols) limited-cols (take column-limit cols)] (cons (query-results->header-row remapping-lookup limited-cols bar-column) - (query-results->row-seq timezone remapping-lookup limited-cols (take rows-limit rows) bar-column max-value)))) + (query-results->row-seq timezone-id remapping-lookup limited-cols (take rows-limit rows) bar-column max-value)))) (defn- strong-limit-text [number] [:strong {:style (style/style {:color style/color-gray-3})} (h (common/format-number number))]) @@ -157,17 +157,16 @@ (defmulti render "Render a Pulse as `chart-type` (e.g. `:bar`, `:scalar`, etc.) and `render-type` (either `:inline` or `:attachment`)." - {:arglists '([chart-type render-type timezone card data])} + {:arglists '([chart-type render-type timezone-id card data])} (fn [chart-type _ _ _ _] chart-type)) - (s/defmethod render :table :- common/RenderedPulseCard - [_ render-type timezone card {:keys [cols rows] :as data}] + [_ render-type timezone-id :- (s/maybe s/Str) card {:keys [cols rows] :as data}] (let [table-body [:div (table/render-table (color/make-color-selector data (:visualization_settings card)) (mapv :name (:cols data)) - (prep-for-html-rendering timezone cols rows nil nil cols-limit)) + (prep-for-html-rendering timezone-id cols rows nil nil cols-limit)) (render-truncation-warning cols-limit (count-displayed-columns cols) rows-limit (count rows))]] {:attachments nil @@ -178,7 +177,7 @@ (list table-body))})) (s/defmethod render :bar :- common/RenderedPulseCard - [_ _ timezone card {:keys [cols] :as data}] + [_ _ timezone-id :- (s/maybe s/Str) card {:keys [cols] :as data}] (let [[x-axis-rowfn y-axis-rowfn] (common/graphing-column-row-fns card data) rows (common/non-nil-rows x-axis-rowfn y-axis-rowfn (:rows data)) max-value (apply max (map y-axis-rowfn rows))] @@ -189,29 +188,28 @@ [:div (table/render-table (color/make-color-selector data (:visualization_settings card)) (mapv :name cols) - (prep-for-html-rendering timezone cols rows y-axis-rowfn max-value 2)) + (prep-for-html-rendering timezone-id cols rows y-axis-rowfn max-value 2)) (render-truncation-warning 2 (count-displayed-columns cols) rows-limit (count rows))]})) - (s/defmethod render :scalar :- common/RenderedPulseCard - [_ _ timezone card {:keys [cols rows]}] + [_ _ timezone-id card {:keys [cols rows]}] {:attachments nil :content [:div {:style (style/style (style/scalar-style))} - (h (format-cell timezone (ffirst rows) (first cols)))]}) + (h (format-cell timezone-id (ffirst rows) (first cols)))]}) (s/defmethod render :sparkline :- common/RenderedPulseCard - [_ render-type timezone card {:keys [rows cols] :as data}] + [_ render-type timezone-id card {:keys [rows cols] :as data}] (let [[x-axis-rowfn y-axis-rowfn] (common/graphing-column-row-fns card data) - rows (sparkline/sparkline-rows timezone card data) + rows (sparkline/sparkline-rows timezone-id card data) last-rows (reverse (take-last 2 rows)) values (for [row last-rows] (some-> row y-axis-rowfn common/format-number)) - labels (datetime/format-timestamp-pair timezone (map x-axis-rowfn last-rows) (x-axis-rowfn cols)) - image-bundle (sparkline/sparkline-image-bundle render-type timezone card {:rows rows, :cols cols})] + labels (datetime/format-temporal-string-pair timezone-id (map x-axis-rowfn last-rows) (x-axis-rowfn cols)) + image-bundle (sparkline/sparkline-image-bundle render-type timezone-id card {:rows rows, :cols cols})] {:attachments (when image-bundle (image-bundle/image-bundle->attachment image-bundle)) @@ -242,7 +240,6 @@ :font-size :16px})} (second labels)]]]]})) - (s/defmethod render :empty :- common/RenderedPulseCard [_ render-type _ _ _] (let [image-bundle (image-bundle/no-results-image-bundle render-type)] @@ -259,7 +256,6 @@ :color style/color-gray-4})} (trs "No results")]]})) - (s/defmethod render :attached :- common/RenderedPulseCard [_ render-type _ _ _] (let [image-bundle (image-bundle/attached-image-bundle render-type)] @@ -276,7 +272,6 @@ :color style/color-gray-4})} (trs "This question has been included as a file attachment")]]})) - (s/defmethod render :unknown :- common/RenderedPulseCard [_ _ _ _ _] {:attachments @@ -291,7 +286,6 @@ [:br] (trs "Please view this card in Metabase.")]}) - (s/defmethod render :error :- common/RenderedPulseCard [_ _ _ _ _] {:attachments diff --git a/src/metabase/pulse/render/datetime.clj b/src/metabase/pulse/render/datetime.clj index 130078dde59..e929c1bdae9 100644 --- a/src/metabase/pulse/render/datetime.clj +++ b/src/metabase/pulse/render/datetime.clj @@ -1,49 +1,40 @@ (ns metabase.pulse.render.datetime "Logic for rendering datetimes inside Pulses." - (:require [clj-time - [core :as t] - [format :as f]] + (:require [clojure.tools.logging :as log] + [java-time :as t] [metabase.util - [date :as du] - [i18n :refer [tru]] + [date-2 :as u.date] + [i18n :refer [trs tru]] [schema :as su]] [schema.core :as s]) - (:import [org.joda.time DateTime DateTimeZone] - [org.joda.time.base BaseDateTime BaseSingleFieldPeriod])) + (:import java.time.Period + java.time.temporal.Temporal)) -(defn- reformat-timestamp [timezone old-format-timestamp new-format-string] - (f/unparse (f/with-zone (f/formatter new-format-string) - (DateTimeZone/forTimeZone timezone)) - (du/str->date-time old-format-timestamp timezone))) +(defn- reformat-temporal-str [timezone-id s new-format-string] + (t/format new-format-string (u.date/parse s timezone-id))) -(defn format-timestamp - "Formats timestamps with human friendly absolute dates based on the column :unit" - [timezone timestamp col] +(defn format-temporal-str + "Reformat a temporal literal string `s` (i.e., an ISO-8601 string) with a human-friendly format based on the + column `:unit`." + [timezone-id s col] (case (:unit col) - :hour (reformat-timestamp timezone timestamp "h a - MMM YYYY") - :week (str "Week " (reformat-timestamp timezone timestamp "w - YYYY")) - :month (reformat-timestamp timezone timestamp "MMMM YYYY") - :quarter (let [timestamp-obj (du/str->date-time timestamp timezone)] - (str "Q" - (inc (int (/ (t/month timestamp-obj) - 3))) - " - " - (t/year timestamp-obj))) + ;; these types have special formatting + :hour (reformat-temporal-str timezone-id s "h a - MMM YYYY") + :week (str "Week " (reformat-temporal-str timezone-id s "w - YYYY")) + :month (reformat-temporal-str timezone-id s "MMMM YYYY") + :quarter (reformat-temporal-str timezone-id s "QQQ - YYYY") + ;; no special formatting here : return as ISO-8601 ;; TODO: probably shouldn't even be showing sparkline for x-of-y groupings? (:year :hour-of-day :day-of-week :week-of-year :month-of-year) - (str timestamp) + s - (reformat-timestamp timezone timestamp "MMM d, YYYY"))) - - -(defn- year [] (t/year (t/now))) -(defn- month [] (t/month (t/now))) -(defn- day [] (t/day (t/now))) + ;; for everything else return in this format + (reformat-temporal-str timezone-id s "MMM d, YYYY"))) (def ^:private RenderableInterval - {:interval-start BaseDateTime - :interval BaseSingleFieldPeriod + {:interval-start Temporal + :interval Period :this-interval-name su/NonBlankString :last-interval-name su/NonBlankString}) @@ -55,13 +46,13 @@ (s/defmethod renderable-interval :day :- RenderableInterval [_] - {:interval-start (t/date-midnight (year) (month) (day)) + {:interval-start (u.date/truncate :day) :interval (t/days 1) :this-interval-name (tru "Today") :last-interval-name (tru "Yesterday")}) (defn- start-of-this-week [] - (-> (org.joda.time.LocalDate. (t/now)) .weekOfWeekyear .roundFloorCopy .toDateTimeAtStartOfDay)) + (u.date/truncate :week)) (s/defmethod renderable-interval :week :- RenderableInterval [_] @@ -72,33 +63,30 @@ (s/defmethod renderable-interval :month :- RenderableInterval [_] - {:interval-start (t/date-midnight (year) (month)) + {:interval-start (u.date/truncate :month) :interval (t/months 1) :this-interval-name (tru "This month") :last-interval-name (tru "Last month")}) -(defn- start-of-this-quarter [] - (t/date-midnight (year) (inc (* 3 (Math/floor (/ (dec (month)) - 3)))))) (s/defmethod renderable-interval :quarter :- RenderableInterval [_] - {:interval-start (start-of-this-quarter) + {:interval-start (u.date/truncate :quarter) :interval (t/months 3) :this-interval-name (tru "This quarter") :last-interval-name (tru "Last quarter")}) (s/defmethod renderable-interval :year :- RenderableInterval [_] - {:interval-start (t/date-midnight (year)) + {:interval-start (u.date/truncate :year) :interval (t/years 1) :this-interval-name (tru "This year") :last-interval-name (tru "Last year")}) (s/defn ^:private date->interval-name :- (s/maybe su/NonBlankString) - [date :- (s/maybe DateTime), unit :- (s/maybe s/Keyword)] - (when (and date unit) + [t :- (s/maybe Temporal), unit :- (s/maybe s/Keyword)] + (when (and t unit) (when-let [{:keys [interval-start interval this-interval-name last-interval-name]} (renderable-interval unit)] - (condp t/within? date + (condp t/contains? t (t/interval interval-start (t/plus interval-start interval)) this-interval-name @@ -107,16 +95,23 @@ nil)))) -(s/defn format-timestamp-relative :- (s/maybe su/NonBlankString) +(s/defn format-temporal-str-relative :- (s/maybe su/NonBlankString) "Formats timestamps with relative names (today, yesterday, this *, last *) based on column :unit, if possible, otherwie returns nil" - [timezone timestamp-str {:keys [unit]}] - (date->interval-name (du/str->date-time timestamp-str timezone) unit)) - -(defn format-timestamp-pair - "Formats a pair of timestamps, using relative formatting for the first timestamps if possible and 'Previous :unit' for - the second, otherwise absolute timestamps for both" - [timezone [a b] col] - (if-let [a' (format-timestamp-relative timezone a col)] - [a' (str "Previous " (-> col :unit name))] - [(format-timestamp timezone a col) (format-timestamp timezone b col)])) + [timezone-id s {:keys [unit]}] + (date->interval-name (u.date/parse s timezone-id) unit)) + +(defn format-temporal-string-pair + "Formats a pair of temporal string literals (i.e., ISO-8601 strings) using relative formatting for the first + temporal values if possible, and 'Previous :unit' for the second; otherwise absolute instants in time for both." + [timezone-id [a b] col] + {:pre [((some-fn nil? string?) timezone-id)]} + (try + (if-let [a' (format-temporal-str-relative timezone-id a col)] + [a' (tru "Previous {0}" (-> col :unit name))] + [(format-temporal-str timezone-id a col) (format-temporal-str timezone-id b col)]) + (catch Throwable _ + ;; TODO - there is code that calls this in `render.body` regardless of the types of values + (log/warn (trs "FIXME: These aren''t valid temporal literals: {0} {1}. Why are we attemping to format them as such?" + (pr-str a) (pr-str b))) + nil))) diff --git a/src/metabase/pulse/render/sparkline.clj b/src/metabase/pulse/render/sparkline.clj index 32f2ff3d869..447fb3608cc 100644 --- a/src/metabase/pulse/render/sparkline.clj +++ b/src/metabase/pulse/render/sparkline.clj @@ -1,16 +1,19 @@ (ns metabase.pulse.render.sparkline - (:require [metabase.pulse.render + (:require [java-time :as t] + [metabase.pulse.render [common :as common] [image-bundle :as image-bundle] [style :as style]] [metabase.types :as types] [metabase.util - [date :as du] - [i18n :refer [tru]]]) + [date-2 :as u.date] + [i18n :refer [tru]]] + [schema.core :as s]) (:import [java.awt BasicStroke Color RenderingHints] java.awt.image.BufferedImage java.io.ByteArrayOutputStream - java.util.Date + [java.time LocalDate LocalDateTime LocalTime OffsetTime] + java.time.temporal.Temporal javax.imageio.ImageIO)) (def ^:private ^:const dot-radius 6) @@ -58,19 +61,26 @@ (throw (Exception. (tru "No appropriate image writer found!")))) (.toByteArray os)))) -;; TIMEZONE FIXME -(defn- format-val-fn [timezone cols x-axis-rowfn] +(defn- format-val-fn [timezone-id cols x-axis-rowfn] (if (types/temporal-field? (x-axis-rowfn cols)) - #(.getTime ^Date (du/->Timestamp % timezone)) + (fn f [x] + (cond + (string? x) (f (u.date/parse x timezone-id)) + (instance? LocalDate x) (f (t/local-date-time x (t/local-time 0))) + (instance? LocalTime x) (f (t/local-date-time (t/local-date "1970-01-01") x)) + (instance? LocalDateTime x) (f (t/offset-date-time x (t/zone-offset 0))) + (instance? OffsetTime x) (f (t/offset-date-time (t/local-date "1970-01-01") x (t/zone-offset x))) + (instance? Temporal x) (java-time/to-millis-from-epoch x) + :else x)) identity)) (defn sparkline-image-bundle "Render a sparkline chart to an image bundle." - [render-type timezone card {:keys [rows cols] :as data}] + [render-type timezone-id card {:keys [rows cols] :as data}] ;; `x-axis-rowfn` and `y-axis-rowfn` are functions that get whatever is at the corresponding index (let [[x-axis-rowfn y-axis-rowfn] (common/graphing-column-row-fns card data) - format-val (format-val-fn timezone cols x-axis-rowfn) + format-val (format-val-fn timezone-id cols x-axis-rowfn) x-axis-values (let [x-axis-values (map (comp format-val x-axis-rowfn) rows) xmin (apply min x-axis-values) xmax (apply max x-axis-values) @@ -89,12 +99,12 @@ (image-bundle/make-image-bundle render-type (render-sparkline-to-png x-axis-values y-axis-values)))) -(defn sparkline-rows +(s/defn sparkline-rows "Get sorted rows from query results, with nils removed, appropriate for rendering as a sparkline." - [timezone card {:keys [rows cols], :as data}] + [timezone-id :- (s/maybe s/Str) card {:keys [rows cols], :as data}] (let [[x-axis-rowfn y-axis-rowfn] (common/graphing-column-row-fns card data) - format-val (format-val-fn timezone cols x-axis-rowfn)] + format-val (format-val-fn timezone-id cols x-axis-rowfn)] (common/non-nil-rows x-axis-rowfn y-axis-rowfn diff --git a/src/metabase/query_processor/middleware/async.clj b/src/metabase/query_processor/middleware/async.clj index 9142a86cfe5..30324d29cac 100644 --- a/src/metabase/query_processor/middleware/async.clj +++ b/src/metabase/query_processor/middleware/async.clj @@ -2,11 +2,11 @@ "Middleware for implementing async QP behavior." (:require [clojure.core.async :as a] [clojure.tools.logging :as log] + [metabase + [config :as config] + [util :as u]] [metabase.async.util :as async.u] - [metabase.config :as config] - [metabase.util - [date :as du] - [i18n :refer [trs tru]]]) + [metabase.util.i18n :refer [trs tru]]) (:import java.util.concurrent.TimeoutException)) ;;; +----------------------------------------------------------------------------------------------------------------+ @@ -73,9 +73,9 @@ ;; prod but for test and dev purposes we want to fail faster because it usually means I broke something in the QP ;; code (cond - config/is-prod? (du/minutes->ms 20) - config/is-test? (du/seconds->ms 30) - config/is-dev? (du/minutes->ms 3))) + config/is-prod? (u/minutes->ms 20) + config/is-test? (u/seconds->ms 30) + config/is-dev? (u/minutes->ms 3))) (defn- wait-for-result [out-chan] (let [[result port] (a/alts!! [out-chan (a/timeout query-timeout-ms)])] @@ -86,7 +86,7 @@ (not= port out-chan) (do (a/close! out-chan) - (throw (TimeoutException. (tru "Query timed out after {0}" (du/format-milliseconds query-timeout-ms))))) + (throw (TimeoutException. (tru "Query timed out after {0}" (u/format-milliseconds query-timeout-ms))))) :else result))) diff --git a/src/metabase/query_processor/middleware/cache.clj b/src/metabase/query_processor/middleware/cache.clj index 723dd20e1f8..565a6001006 100644 --- a/src/metabase/query_processor/middleware/cache.clj +++ b/src/metabase/query_processor/middleware/cache.clj @@ -21,9 +21,7 @@ [metabase.plugins.classloader :as classloader] [metabase.query-processor.middleware.cache-backend.interface :as i] [metabase.query-processor.util :as qputil] - [metabase.util - [date :as du] - [i18n :refer [trs]]])) + [metabase.util.i18n :refer [trs]])) ;; TODO - Why not make this an option in the query itself? :confused: (def ^:dynamic ^Boolean *ignore-cached-results* @@ -78,7 +76,7 @@ (defn- cached-results [query-hash max-age-seconds] (when-not *ignore-cached-results* (when-let [results (i/cached-results @backend-instance query-hash max-age-seconds)] - (assert (du/is-temporal? (:updated_at results)) + (assert (instance? java.time.temporal.Temporal (:updated_at results)) "cached-results should include an `:updated_at` field containing the date when the query was last ran.") (log/info "Returning cached results for query" (u/emoji "💾")) (assoc results :cached true)))) diff --git a/src/metabase/query_processor/middleware/cache_backend/db.clj b/src/metabase/query_processor/middleware/cache_backend/db.clj index d51adb1ee55..89154e0dd8a 100644 --- a/src/metabase/query_processor/middleware/cache_backend/db.clj +++ b/src/metabase/query_processor/middleware/cache_backend/db.clj @@ -1,31 +1,29 @@ (ns metabase.query-processor.middleware.cache-backend.db (:require [clojure.tools.logging :as log] + [java-time :as t] [metabase [public-settings :as public-settings] [util :as u]] [metabase.models.query-cache :refer [QueryCache]] [metabase.query-processor.middleware.cache-backend.interface :as i] - [metabase.util.date :as du] [taoensso.nippy :as nippy] [toucan.db :as db]) (:import [java.io BufferedOutputStream ByteArrayOutputStream DataOutputStream] java.util.zip.GZIPOutputStream)) (defn- cached-results - "Return cached results for QUERY-HASH if they exist and are newer than MAX-AGE-SECONDS." + "Return cached results for `query-hash` if they exist and are newer than `max-age-seconds`." [query-hash max-age-seconds] (when-let [{:keys [results updated_at]} (db/select-one [QueryCache :results :updated_at] :query_hash query-hash - :updated_at [:>= (du/->Timestamp (- (System/currentTimeMillis) - (* 1000 max-age-seconds)))])] + :updated_at [:>= (t/minus (t/instant) (t/seconds max-age-seconds))])] (assoc results :updated_at updated_at))) (defn- purge-old-cache-entries! "Delete any cache entries that are older than the global max age `max-cache-entry-age-seconds` (currently 3 months)." [] (db/simple-delete! QueryCache - :updated_at [:<= (du/->Timestamp (- (System/currentTimeMillis) - (* 1000 (public-settings/query-caching-max-ttl))))])) + :updated_at [:<= (t/minus (t/instant) (t/seconds (public-settings/query-caching-max-ttl)))])) (defn- throw-if-max-exceeded [max-num-bytes bytes-in-flight] (when (< max-num-bytes bytes-in-flight) @@ -75,7 +73,7 @@ (throw e))))) (defn- save-results! - "Save the RESULTS of query with QUERY-HASH, updating an existing QueryCache entry + "Save the `results` of query with `query-hash`, updating an existing QueryCache entry if one already exists, otherwise creating a new entry." [query-hash results] ;; Explicitly compressing the results here rather than having Toucan compress it automatically. This allows us to @@ -86,7 +84,7 @@ (do (purge-old-cache-entries!) (or (db/update-where! QueryCache {:query_hash query-hash} - :updated_at (du/new-sql-timestamp) + :updated_at :%now :results compressed-results) (db/insert! QueryCache :query_hash query-hash diff --git a/src/metabase/query_processor/middleware/optimize_datetime_filters.clj b/src/metabase/query_processor/middleware/optimize_datetime_filters.clj index 40ce6aeff53..6ef71afeaf3 100644 --- a/src/metabase/query_processor/middleware/optimize_datetime_filters.clj +++ b/src/metabase/query_processor/middleware/optimize_datetime_filters.clj @@ -1,7 +1,8 @@ (ns metabase.query-processor.middleware.optimize-datetime-filters "Middlware that optimizes equality (`=` and `!=`) and comparison (`<`, `between`, etc.) filter clauses against bucketed datetime fields. See docstring for `optimize-datetime-filters` for more details." - (:require [metabase.mbql.util :as mbql.u] + (:require [clojure.tools.logging :as log] + [metabase.mbql.util :as mbql.u] [metabase.util.date-2 :as u.date])) (def ^:private optimizable-units @@ -63,26 +64,26 @@ (mbql.u/negate-filter-clause ((get-method optimize-filter :=) filter-clause))) (defn- optimize-comparison-filter - [trunc-fn [filter-type field [_ inst unit]]] - [filter-type + [trunc-fn [filter-type field [_ inst unit]] new-filter-type] + [new-filter-type (change-datetime-field-unit-to-default field) [:absolute-datetime (trunc-fn unit inst) :default]]) (defmethod optimize-filter :< [filter-clause] - (optimize-comparison-filter lower-bound filter-clause)) + (optimize-comparison-filter lower-bound filter-clause :<)) (defmethod optimize-filter :<= [filter-clause] - (optimize-comparison-filter lower-bound filter-clause)) + (optimize-comparison-filter upper-bound filter-clause :<)) (defmethod optimize-filter :> [filter-clause] - (optimize-comparison-filter upper-bound filter-clause)) + (optimize-comparison-filter upper-bound filter-clause :>=)) (defmethod optimize-filter :>= [filter-clause] - (optimize-comparison-filter upper-bound filter-clause)) + (optimize-comparison-filter lower-bound filter-clause :>=)) (defmethod optimize-filter :between [[_ field [_ lower unit] [_ upper]]] @@ -97,7 +98,10 @@ (mbql.u/replace query (_ :guard (partial mbql.u/is-clause? (set (keys (methods optimize-filter))))) (if (can-optimize-filter? &match) - (optimize-filter &match) + (let [optimized (optimize-filter &match)] + (when-not (= &match optimized) + (log/tracef "Optimized filter %s to %s" (pr-str &match) (pr-str optimized))) + optimized) &match)))) (defn optimize-datetime-filters @@ -105,11 +109,11 @@ bucketed datetime fields. Rewrites those filter clauses as logically equivalent filter clauses that do not use bucketing (i.e., their datetime unit is `:default`, meaning no bucketing functions need be applied). - [:= [:datetime-field [:field-id 1] :month] [:absolute-datetime #inst \"2019-09-01\" :month]] + [:= [:datetime-field [:field-id 1] :month] [:absolute-datetime #t \"2019-09-01\" :month]] -> [:and - [:>= [:datetime-field [:field-id 1] :default] [:absolute-datetime #inst \"2019-09-01\" :month]] - [:< [:datetime-field [:field-id 1] :default] [:absolute-datetime #inst \"2019-10-01\" :month]]] + [:>= [:datetime-field [:field-id 1] :default] [:absolute-datetime #t \"2019-09-01\" :month]] + [:< [:datetime-field [:field-id 1] :default] [:absolute-datetime #t \"2019-10-01\" :month]]] The equivalent SQL, before and after, looks like: diff --git a/src/metabase/query_processor/middleware/parameters/dates.clj b/src/metabase/query_processor/middleware/parameters/dates.clj index 92128c390b7..b1f9714f1a6 100644 --- a/src/metabase/query_processor/middleware/parameters/dates.clj +++ b/src/metabase/query_processor/middleware/parameters/dates.clj @@ -3,15 +3,14 @@ TIMEZONE FIXME - this whole thing should be rewritten to use `java-time` and the new date util namespace. I started implementing this in `metabase.query-processor.middleware.parameters.dates-2`, but it is not yet finished." - (:require [clj-time - [core :as t] - [format :as tf]] + (:require [java-time :as t] [medley.core :as m] [metabase.mbql.schema :as mbql.s] [metabase.models.params :as params] - [metabase.util.schema :as su] - [schema.core :as s]) - (:import [org.joda.time DateTime DateTimeConstants])) + [metabase.util + [date-2 :as u.date] + [schema :as su]] + [schema.core :as s])) (s/defn date-type? "Is param type `:date` or some subtype like `:date/month-year`?" @@ -27,38 +26,36 @@ ;; hour/minute granularity in field parameter queries. (defn- day-range - [^DateTime start, ^DateTime end] - {:end end - :start start}) - -(defn- week-range - [^DateTime start, ^DateTime end] - ;; weeks always start on SUNDAY and end on SATURDAY - ;; NOTE: in Joda the week starts on Monday and ends on Sunday, so to get the right Sunday we rollback 1 week - {:end (.withDayOfWeek end DateTimeConstants/SATURDAY) - :start (.withDayOfWeek ^DateTime (t/minus start (t/weeks 1)) DateTimeConstants/SUNDAY)}) - -(defn- month-range - [^DateTime start, ^DateTime end] - {:end (t/last-day-of-the-month end) - :start (t/first-day-of-the-month start)}) - -(defn- year-range - [^DateTime start, ^DateTime end] - {:end (t/last-day-of-the-month (.withMonthOfYear end DateTimeConstants/DECEMBER)) - :start (t/first-day-of-the-month (.withMonthOfYear start DateTimeConstants/JANUARY))}) - -(defn- start-of-quarter [quarter year] - (t/first-day-of-the-month (.withMonthOfYear (t/date-time year) (case quarter - "Q1" DateTimeConstants/JANUARY - "Q2" DateTimeConstants/APRIL - "Q3" DateTimeConstants/JULY - "Q4" DateTimeConstants/OCTOBER)))) + [start end] + {:start start, :end end}) + +(defn- comparison-range + ([t unit] + (comparison-range t t unit)) + + ([start end unit] + (merge + (u.date/comparison-range start unit :>= {:resolution :day}) + (u.date/comparison-range end unit :<= {:resolution :day, :end :inclusive})))) + +(defn- week-range [start end] + (comparison-range start end :week)) + +(defn- month-range [start end] + (comparison-range start end :month)) + +(defn- year-range [start end] + (comparison-range start end :year)) + (defn- quarter-range [quarter year] - (let [dt (start-of-quarter quarter year)] - {:end (t/last-day-of-the-month (t/plus dt (t/months 2))) - :start (t/first-day-of-the-month dt)})) + (let [year-quarter (t/year-quarter year (case quarter + "Q1" 1 + "Q2" 2 + "Q3" 3 + "Q4" 4))] + {:start (.atDay year-quarter 1) + :end (.atEndOfQuarter year-quarter)})) (def ^:private operations-by-date-unit {"day" {:unit-range day-range @@ -70,10 +67,6 @@ "year" {:unit-range year-range :to-period t/years}}) -(defn- parse-absolute-date - [date] - (tf/parse (tf/formatters :date-opt-time) date)) - ;;; +----------------------------------------------------------------------------------------------------------------+ ;;; | DATE STRING DECODERS | @@ -87,7 +80,7 @@ :unit (conj (seq (get operations-by-date-unit group-value)) [group-label group-value]) :int-value [[group-label (Integer/parseInt group-value)]] - (:date :date-1 :date-2) [[group-label (parse-absolute-date group-value)]] + (:date :date-1 :date-2) [[group-label (u.date/parse group-value)]] [[group-label group-value]])) @@ -152,13 +145,13 @@ :filter (fn [{:keys [unit]} field] [:time-interval field :current (keyword unit)])}]) -(defn- day->iso8601 [date] - (tf/unparse (tf/formatters :year-month-day) date)) +(defn- ->iso-8601-date [t] + (t/format :iso-local-date t)) ;; TODO - using `range->filter` so much below seems silly. Why can't we just bucket the field and use `:=` clauses? (defn- range->filter [{:keys [start end]} field] - [:between [:datetime-field field :day] (day->iso8601 start) (day->iso8601 end)]) + [:between [:datetime-field field :day] (->iso-8601-date start) (->iso-8601-date end)]) (def ^:private absolute-date-string-decoders ;; year and month @@ -179,26 +172,26 @@ :range (fn [{:keys [date]} _] {:start date, :end date}) :filter (fn [{:keys [date]} field-id-clause] - (let [iso8601date (day->iso8601 date)] + (let [iso8601date (->iso-8601-date date)] [:= [:datetime-field field-id-clause :day] iso8601date]))} ;; day range {:parser (regex->parser #"([0-9-T:]+)~([0-9-T:]+)" [:date-1 :date-2]) :range (fn [{:keys [date-1 date-2]} _] {:start date-1, :end date-2}) :filter (fn [{:keys [date-1 date-2]} field-id-clause] - [:between [:datetime-field field-id-clause :day] (day->iso8601 date-1) (day->iso8601 date-2)])} + [:between [:datetime-field field-id-clause :day] (->iso-8601-date date-1) (->iso-8601-date date-2)])} ;; before day {:parser (regex->parser #"~([0-9-T:]+)" [:date]) :range (fn [{:keys [date]} _] {:end date}) :filter (fn [{:keys [date]} field-id-clause] - [:< [:datetime-field field-id-clause :day] (day->iso8601 date)])} + [:< [:datetime-field field-id-clause :day] (->iso-8601-date date)])} ;; after day {:parser (regex->parser #"([0-9-T:]+)~" [:date]) :range (fn [{:keys [date]} _] {:start date}) :filter (fn [{:keys [date]} field-id-clause] - [:> [:datetime-field field-id-clause :day] (day->iso8601 date)])}]) + [:> [:datetime-field field-id-clause :day] (->iso-8601-date date)])}]) (def ^:private all-date-string-decoders (concat relative-date-string-decoders absolute-date-string-decoders)) @@ -215,19 +208,18 @@ (defn date-string->range "Takes a string description of a date range such as 'lastmonth' or '2016-07-15~2016-08-6' and return a MAP with `:start` and `:end` as iso8601 string formatted dates, respecting the given timezone." - [date-string report-timezone] - (let [tz (t/time-zone-for-id report-timezone) - formatter-local-tz (tf/formatter "yyyy-MM-dd" tz) - formatter-no-tz (tf/formatter "yyyy-MM-dd") - today (.withTimeAtStartOfDay (t/to-time-zone (t/now) tz))] + [date-string report-timezone-id] + (let [#_formatter-local-tz #_ (u.date/parse % report-timezone-id) + #_formatter-no-tz #_ (tf/formatter "yyyy-MM-dd") + today (t/local-date)] ;; Relative dates respect the given time zone because a notion like "last 7 days" might mean a different range of ;; days depending on the user timezone (or (->> (execute-decoders relative-date-string-decoders :range today date-string) - (m/map-vals (partial tf/unparse formatter-local-tz))) + (m/map-vals u.date/format #_(partial tf/unparse formatter-local-tz))) ;; Absolute date ranges don't need the time zone conversion because in SQL the date ranges are compared ;; against the db field value that is casted granularity level of a day in the db time zone (->> (execute-decoders absolute-date-string-decoders :range nil date-string) - (m/map-vals (partial tf/unparse formatter-no-tz)))))) + (m/map-vals u.date/format #_(partial tf/unparse formatter-no-tz)))))) (s/defn date-string->filter :- mbql.s/Filter "Takes a string description of a *date* (not datetime) range such as 'lastmonth' or '2016-07-15~2016-08-6' and diff --git a/src/metabase/query_processor/middleware/parameters/native/interface.clj b/src/metabase/query_processor/middleware/parameters/native/interface.clj index 16cf38a1915..aebe1cd79be 100644 --- a/src/metabase/query_processor/middleware/parameters/native/interface.clj +++ b/src/metabase/query_processor/middleware/parameters/native/interface.clj @@ -14,7 +14,7 @@ ;; * A map contianing the value and type info for the value, e.g. ;; ;; {:type :date/single -;; :value #inst "2019-09-20T19:52:00.000-07:00"} +;; :value #t "2019-09-20T19:52:00.000-07:00"} ;; ;; * A vector of maps like the one above (for multiple values) (p.types/defrecord+ FieldFilter [field value] diff --git a/src/metabase/query_processor/middleware/parameters/native/substitution.clj b/src/metabase/query_processor/middleware/parameters/native/substitution.clj index 1a8dd59271e..3af2d839940 100644 --- a/src/metabase/query_processor/middleware/parameters/native/substitution.clj +++ b/src/metabase/query_processor/middleware/parameters/native/substitution.clj @@ -5,7 +5,7 @@ {;; appropriate SQL that should be used to replace the param snippet, e.g. {{x}} :replacement-snippet \"= ?\" ;; ; any prepared statement args (values for `?` placeholders) needed for the replacement snippet - :prepared-statement-args [#inst \"2017-01-01\"]}" + :prepared-statement-args [#t \"2017-01-01\"]}" (:require [clojure.string :as str] [honeysql.core :as hsql] [metabase.driver :as driver] diff --git a/src/metabase/query_processor/middleware/parameters/native/values.clj b/src/metabase/query_processor/middleware/parameters/native/values.clj index 9b33f61c660..e49f29a82ec 100644 --- a/src/metabase/query_processor/middleware/parameters/native/values.clj +++ b/src/metabase/query_processor/middleware/parameters/native/values.clj @@ -107,7 +107,7 @@ ;; `value-info` will look something like after we remove `:target` which is not needed after this point ;; ;; {:type :date/single - ;; :value #inst "2019-09-20T19:52:00.000-07:00"} + ;; :value #t "2019-09-20T19:52:00.000-07:00"} ;; ;; (or it will be a vector of these maps for multiple values) (cond @@ -221,7 +221,7 @@ (query->params-map some-query) -> - {:checkin_date #inst \"2019-09-19T23:30:42.233-07:00\"}" + {:checkin_date #t \"2019-09-19T23:30:42.233-07:00\"}" [{tags :template-tags, params :parameters}] (into {} (for [[k tag] tags :let [v (value-for-tag tag params)] diff --git a/src/metabase/query_processor/middleware/process_userland_query.clj b/src/metabase/query_processor/middleware/process_userland_query.clj index 2906d760c89..d9844585990 100644 --- a/src/metabase/query_processor/middleware/process_userland_query.clj +++ b/src/metabase/query_processor/middleware/process_userland_query.clj @@ -11,9 +11,7 @@ [interface :as qp.i] [util :as qputil]] [metabase.util :as u] - [metabase.util - [date :as du] - [i18n :refer [deferred-tru trs tru]]] + [metabase.util.i18n :refer [deferred-tru trs tru]] [toucan.db :as db])) (defn- add-running-time [{start-time-ms :start_time_millis, :as query-execution}] @@ -142,7 +140,7 @@ :hash query-hash :native (= (keyword query-type) :native) :json_query (dissoc query :info) - :started_at (du/new-sql-timestamp) + :started_at :%now :running_time 0 :result_rows 0 :start_time_millis (System/currentTimeMillis)}) diff --git a/src/metabase/query_processor/timezone.clj b/src/metabase/query_processor/timezone.clj index d23e4dc4770..028621e712f 100644 --- a/src/metabase/query_processor/timezone.clj +++ b/src/metabase/query_processor/timezone.clj @@ -18,8 +18,11 @@ "Impl for `with-report-timezone-id`." [timezone-id thunk] {:pre [((some-fn nil? string?) timezone-id)]} - ;; TODO - HACKY. Not sure if we want this. - (#'driver/notify-all-databases-updated) + ;; This will fail if the app DB isn't initialized yet. That's fine — there's no DBs to notify if the app DB isn't + ;; set up. + (try + (#'driver/notify-all-databases-updated) + (catch Throwable _)) (binding [*report-timezone-id-override* (or timezone-id ::nil)] (thunk))) @@ -67,10 +70,6 @@ (or *report-timezone-id-override* (driver/report-timezone))) -(defn- database-timezone-id* [] - (or *database-timezone-id-override* - (:timezone (qp.store/database)))) - ;;; +----------------------------------------------------------------------------------------------------------------+ ;;; | Public Interface | @@ -79,15 +78,22 @@ (defn report-timezone-id-if-supported "Timezone ID for the report timezone, if the current driver supports it. (If the current driver supports it, this is bound by the `bind-effective-timezone` middleware.)" - ^String [] - (when (driver/supports? driver/*driver* :set-timezone) - (valid-timezone-id (report-timezone-id*)))) + (^String [] + (report-timezone-id-if-supported driver/*driver*)) + + (^String [driver] + (when (driver/supports? driver :set-timezone) + (valid-timezone-id (report-timezone-id*))))) (defn database-timezone-id "The timezone that the current database is in, as determined by the most recent sync." - ^String [] - (valid-timezone-id - (database-timezone-id*))) + (^String [] + (database-timezone-id ::db-from-store)) + + (^String [database] + (valid-timezone-id + (or *database-timezone-id-override* + (:timezone (if (= database ::db-from-store) (qp.store/database) database)))))) (defn system-timezone-id "The system timezone of this Metabase instance." @@ -105,12 +111,20 @@ "The timezone that a query is actually ran in -- report timezone, if set and supported by the current driver; otherwise the timezone of the database (if known), otherwise the system timezone. Guaranteed to always return a timezone ID — never returns `nil`." - ^String [] - (valid-timezone-id - (or *results-timezone-id-override* - (report-timezone-id-if-supported) - (database-timezone-id) - ;; NOTE: if we don't have an explicit report-timezone then use the JVM timezone - ;; this ensures alignment between the way dates are processed by JDBC and our returned data - ;; GH issues: #2282, #2035 - (system-timezone-id)))) + (^String [] + (results-timezone-id driver/*driver* ::db-from-store)) + + (^String [database] + (results-timezone-id (:engine database) database)) + + (^String [driver database] + (valid-timezone-id + (or *results-timezone-id-override* + (report-timezone-id-if-supported driver) + ;; don't actually fetch DB from store unless needed — that way if `*results-timezone-id-override*` is set we + ;; don't need to init a store during tests + (database-timezone-id database) + ;; NOTE: if we don't have an explicit report-timezone then use the JVM timezone + ;; this ensures alignment between the way dates are processed by JDBC and our returned data + ;; GH issues: #2282, #2035 + (system-timezone-id))))) diff --git a/src/metabase/sync/analyze.clj b/src/metabase/sync/analyze.clj index ffb975ddfeb..0a4d07b4f94 100644 --- a/src/metabase/sync/analyze.clj +++ b/src/metabase/sync/analyze.clj @@ -12,9 +12,7 @@ [classify :as classify] [fingerprint :as fingerprint]] [metabase.util :as u] - [metabase.util - [date :as du] - [i18n :refer [trs]]] + [metabase.util.i18n :refer [trs]] [schema.core :as s] [toucan.db :as db])) @@ -63,7 +61,7 @@ (db/update-where! Field {:table_id [:in ids] :fingerprint_version i/latest-fingerprint-version :last_analyzed nil} - :last_analyzed (du/new-sql-timestamp)))) + :last_analyzed :%now))) (s/defn ^:private update-fields-last-analyzed! "Update the `last_analyzed` date for all the recently re-fingerprinted/re-classified Fields in TABLE." diff --git a/src/metabase/sync/analyze/fingerprint/fingerprinters.clj b/src/metabase/sync/analyze/fingerprint/fingerprinters.clj index e2449cb5f3d..dc2f48b162f 100644 --- a/src/metabase/sync/analyze/fingerprint/fingerprinters.clj +++ b/src/metabase/sync/analyze/fingerprint/fingerprinters.clj @@ -2,22 +2,21 @@ "Non-identifying fingerprinters for various field types." (:require [bigml.histogram.core :as hist] [cheshire.core :as json] - [clj-time.coerce :as t.coerce] [java-time :as t] [kixi.stats [core :as stats] [math :as math]] [metabase.models.field :as field] - [metabase.query-processor.timezone :as qp.timezone] [metabase.sync.analyze.classifiers.name :as classify.name] [metabase.sync.util :as sync-util] [metabase.util :as u] [metabase.util - [date :as du] + [date-2 :as u.date] [i18n :refer [trs]]] [redux.core :as redux]) (:import com.bigml.histogram.Histogram - com.clearspring.analytics.stream.cardinality.HyperLogLogPlus)) + com.clearspring.analytics.stream.cardinality.HyperLogLogPlus + java.time.temporal.Temporal)) (defn col-wise "Apply reducing functinons `rfs` coll-wise to a seq of seqs." @@ -59,7 +58,7 @@ {:arglists '([field])} (fn [{:keys [base_type special_type unit] :as field}] [(cond - (du/date-extract-units unit) :type/Integer + (u.date/extract-units unit) :type/Integer (field/unix-timestamp? field) :type/DateTime ;; for historical reasons the Temporal fingerprinter is still called `:type/DateTime` so anything that derives ;; from `Temporal` (such as DATEs and TIMEs) should still use the `:type/DateTime` fingerprinter @@ -137,55 +136,37 @@ (trs "Error generating fingerprint for {0}" (sync-util/name-for-logging field#)))))) (defn- earliest - ([] (java.util.Date. Long/MAX_VALUE)) + ([] nil) ([acc] - (when (not= acc (earliest)) - (du/date->iso-8601 acc))) - ([^java.util.Date acc dt] - (if dt - (if (.before ^java.util.Date dt acc) - dt - acc) - acc))) + (some-> acc u.date/format)) + ([acc t] + (if (and t acc (t/before? t acc)) + t + (or acc t)))) (defn- latest - ([] (java.util.Date. 0)) + ([] nil) ([acc] - (when (not= acc (latest)) - (du/date->iso-8601 acc))) - ([^java.util.Date acc dt] - (if dt - (if (.after ^java.util.Date dt acc) - dt - acc) - acc))) - -(defprotocol IDateCoercible - "Protocol for converting objects in resultset to `java.util.Date`" - (->date ^java.util.Date [this] - "Coerce object to a `java.util.Date`.")) - -(defn- date-coercion-timezone-id [] - ;; TODO - if `database-timezone-id` isn't bound we should probably throw an Exception or at the very least log a - ;; warning - (t/zone-id (or (qp.timezone/database-timezone-id) "UTC"))) - -(extend-protocol IDateCoercible - nil (->date [_] nil) - String (->date [this] (-> this du/str->date-time t.coerce/to-date)) - ;; TIMEZONE FIXME — update the fingerprint code to use new `java.time` classes directly, and remove support for - ;; `java.util.Date` and Joda-Time types - java.util.Date (->date [this] this) - org.joda.time.DateTime (->date [this] (t.coerce/to-date this)) - Long (->date [^Long this] (java.util.Date. this)) - Integer (->date [^Integer this] (java.util.Date. (long this))) - java.time.temporal.Temporal (->date [this] (t/to-java-date this)) - java.time.LocalDate (->date [this] (t/to-java-date (t/zoned-date-time this (t/local-time 0) (date-coercion-timezone-id)))) - java.time.LocalTime (->date [this] (t/to-java-date (t/zoned-date-time (t/local-date 1970 1 1) this (date-coercion-timezone-id)))) - java.time.LocalDateTime (->date [this] (t/to-java-date (t/zoned-date-time this (date-coercion-timezone-id))))) + (some-> acc u.date/format)) + ([acc t] + (if (and t acc (t/after? t acc)) + t + (or acc t)))) + +(defprotocol ^:private ITemporalCoerceable + "Protocol for converting objects in resultset to a `java.time` temporal type." + (->temporal ^java.time.temporal.Temporal [this] + "Coerce object to a `java.time` temporal type.")) + +(extend-protocol ITemporalCoerceable + nil (->temporal [_] nil) + String (->temporal [this] (u.date/parse this)) + Long (->temporal [this] (t/instant this)) + Integer (->temporal [this] (t/instant this)) + Temporal (->temporal [this] this)) (deffingerprinter :type/DateTime - ((map ->date) + ((map ->temporal) (redux/fuse {:earliest earliest :latest latest}))) @@ -214,9 +195,9 @@ ((some-fn map? sequential?) (json/parse-string x)))) (deffingerprinter :type/Text - ((map (comp str u/jdbc-clob->str)) ; we cast to str to support `field-literal` type overwriting: - ; `[:field-literal "A_NUMBER" :type/Text]` (which still - ; returns numbers in the result set) + ((map str) ; we cast to str to support `field-literal` type overwriting: + ; `[:field-literal "A_NUMBER" :type/Text]` (which still + ; returns numbers in the result set) (redux/fuse {:percent-json (stats/share valid-serialized-json?) :percent-url (stats/share u/url?) :percent-email (stats/share u/email?) diff --git a/src/metabase/sync/analyze/fingerprint/insights.clj b/src/metabase/sync/analyze/fingerprint/insights.clj index a13c61afc75..13bc117ca9f 100644 --- a/src/metabase/sync/analyze/fingerprint/insights.clj +++ b/src/metabase/sync/analyze/fingerprint/insights.clj @@ -1,13 +1,15 @@ (ns metabase.sync.analyze.fingerprint.insights "Deeper statistical analysis of results." - (:require [kixi.stats + (:require [java-time :as t] + [kixi.stats [core :as stats] [math :as math]] [metabase.mbql.util :as mbql.u] [metabase.models.field :as field] [metabase.sync.analyze.fingerprint.fingerprinters :as f] - [metabase.util.date :as du] - [redux.core :as redux])) + [metabase.util.date-2 :as u.date] + [redux.core :as redux]) + (:import [java.time Instant LocalDate LocalDateTime LocalTime OffsetDateTime OffsetTime ZonedDateTime])) (defn- last-n [n] @@ -171,6 +173,19 @@ (when (and from to unit) (about= (- to from) (unit->duration unit)))) +(defn- ->millis-from-epoch [t] + (when t + (condp instance? t + Instant (t/to-millis-from-epoch t) + OffsetDateTime (t/to-millis-from-epoch t) + ZonedDateTime (t/to-millis-from-epoch t) + ;; TODO - really not convinced this behavior makes sense. Not sure what `xfn` below is actually supposed to be + ;; doing? This roughly matches the old behavior when we were using `java.util.Date`. + LocalDate (->millis-from-epoch (t/offset-date-time t (t/local-time 0) (t/zone-offset 0))) + LocalDateTime (->millis-from-epoch (t/offset-date-time t (t/zone-offset 0))) + LocalTime (->millis-from-epoch (t/offset-date-time (t/local-date "1970-01-01") t (t/zone-offset 0))) + OffsetTime (->millis-from-epoch (t/offset-date-time (t/local-date "1970-01-01") t (t/zone-offset t)))))) + (defn- timeseries-insight [{:keys [numbers datetimes]}] (let [datetime (first datetimes) @@ -178,8 +193,8 @@ xfn #(some-> % (nth x-position) ;; at this point in the pipeline, dates are still stings - f/->date - (.getTime) + f/->temporal + ->millis-from-epoch ms->day)] (apply redux/juxt (for [number-col numbers] @@ -217,7 +232,7 @@ (cond (#{:type/FK :type/PK} special_type) :others (= unit :year) :datetimes - (du/date-extract-units unit) :numbers + (u.date/extract-units unit) :numbers (field/unix-timestamp? field) :datetimes (isa? base_type :type/Number) :numbers (isa? base_type :type/Temporal) :datetimes diff --git a/src/metabase/sync/util.clj b/src/metabase/sync/util.clj index 5adf01b4e3a..ab5d570d27c 100644 --- a/src/metabase/sync/util.clj +++ b/src/metabase/sync/util.clj @@ -2,12 +2,10 @@ "Utility functions and macros to abstract away some common patterns and operations across the sync processes, such as logging start/end messages." (:require [buddy.core.hash :as buddy-hash] - [clj-time - [coerce :as tcoerce] - [core :as time]] [clojure.math.numeric-tower :as math] [clojure.string :as str] [clojure.tools.logging :as log] + [java-time :as t] [medley.core :as m] [metabase [driver :as driver] @@ -20,14 +18,14 @@ [metabase.query-processor.interface :as qpi] [metabase.sync.interface :as i] [metabase.util - [date :as du] + [date-2 :as u.date] [i18n :refer [trs]] [schema :as su]] [ring.util.codec :as codec] [schema.core :as s] [taoensso.nippy :as nippy] [toucan.db :as db]) - (:import org.joda.time.DateTime)) + (:import java.time.temporal.Temporal)) ;;; +----------------------------------------------------------------------------------------------------------------+ ;;; | SYNC OPERATION "MIDDLEWARE" | @@ -104,7 +102,7 @@ result (f)] (log-fn (u/format-color 'magenta "FINISHED: %s (%s)" message - (du/format-nanoseconds (- (System/nanoTime) start-time)))) + (u/format-nanoseconds (- (System/nanoTime) start-time)))) result)) (defn- with-start-and-finish-logging @@ -289,12 +287,8 @@ (s/defn calculate-duration-str :- s/Str "Given two datetimes, caculate the time between them, return the result as a string" - [begin-time :- (s/protocol tcoerce/ICoerce) - end-time :- (s/protocol tcoerce/ICoerce)] - (-> (du/calculate-duration begin-time end-time) - ;; Millis -> Nanos - (* 1000000) - du/format-nanoseconds)) + [begin-time :- Temporal, end-time :- Temporal] + (u/format-nanoseconds (.toNanos (t/duration begin-time end-time)))) (def StepSpecificMetadata "A step function can return any metadata and is used by the related LogSummaryFunction to provide step-specific @@ -303,8 +297,8 @@ (def ^:private TimedSyncMetadata "Metadata common to both sync steps and an entire sync/analyze operation run" - {:start-time DateTime - :end-time DateTime}) + {:start-time Temporal + :end-time Temporal}) (def StepRunMetadata "Map with metadata about the step. Contains both generic information like `start-time` and `end-time` and step @@ -342,19 +336,16 @@ :log-summary-fn (when log-summary-fn (comp str log-summary-fn))})) -(defn- datetime->str [datetime] - (du/->iso-8601-datetime datetime "UTC")) - (s/defn run-step-with-metadata :- StepNameWithMetadata "Runs `step` on `database returning metadata from the run" [database :- i/DatabaseInstance {:keys [step-name sync-fn log-summary-fn] :as step} :- StepDefinition] - (let [start-time (time/now) + (let [start-time (t/zoned-date-time) results (with-start-and-finish-debug-logging (trs "step ''{0}'' for {1}" step-name (name-for-logging database)) #(sync-fn database)) - end-time (time/now)] + end-time (t/zoned-date-time)] [step-name (assoc results :start-time start-time :end-time end-time @@ -374,8 +365,8 @@ "# %s\n" "# %s\n") [(trs "Completed {0} on {1}" operation (:name database)) - (trs "Start: {0}" (datetime->str start-time)) - (trs "End: {0}" (datetime->str end-time)) + (trs "Start: {0}" (u.date/format start-time)) + (trs "End: {0}" (u.date/format end-time)) (trs "Duration: {0}" (calculate-duration-str start-time end-time))]) (apply str (for [[step-name {:keys [start-time end-time log-summary-fn] :as step-info}] steps] (apply format (str "# ---------------------------------------------------------------\n" @@ -386,8 +377,8 @@ (when log-summary-fn (format "# %s\n" (log-summary-fn step-info)))) [(trs "Completed step ''{0}''" step-name) - (trs "Start: {0}" (datetime->str start-time)) - (trs "End: {0}" (datetime->str end-time)) + (trs "Start: {0}" (u.date/format start-time)) + (trs "End: {0}" (u.date/format end-time)) (trs "Duration: {0}" (calculate-duration-str start-time end-time))]))) "#################################################################\n")) @@ -413,9 +404,9 @@ {:keys [start-time end-time]} :- SyncOperationOrStepRunMetadata] {:task task-name :db_id (u/get-id database) - :started_at (du/->Timestamp start-time) - :ended_at (du/->Timestamp end-time) - :duration (du/calculate-duration start-time end-time)}) + :started_at start-time + :ended_at end-time + :duration (.toMillis (t/duration start-time end-time))}) (s/defn ^:private store-sync-summary! [operation :- s/Str @@ -437,9 +428,9 @@ [operation :- s/Str database :- i/DatabaseInstance sync-steps :- [StepDefinition]] - (let [start-time (time/now) + (let [start-time (t/zoned-date-time) step-metadata (mapv #(run-step-with-metadata database %) sync-steps) - end-time (time/now) + end-time (t/zoned-date-time) sync-metadata {:start-time start-time :end-time end-time :steps step-metadata}] diff --git a/src/metabase/task/follow_up_emails.clj b/src/metabase/task/follow_up_emails.clj index 7665af8e5b6..763a8f253dc 100644 --- a/src/metabase/task/follow_up_emails.clj +++ b/src/metabase/task/follow_up_emails.clj @@ -1,13 +1,11 @@ (ns metabase.task.follow-up-emails "Tasks which follow up with Metabase users." - (:require [clj-time - [coerce :as c] - [core :as t]] - [clojure.tools.logging :as log] + (:require [clojure.tools.logging :as log] [clojurewerkz.quartzite [jobs :as jobs] [triggers :as triggers]] [clojurewerkz.quartzite.schedule.cron :as cron] + [java-time :as t] [metabase [email :as email] [public-settings :as public-settings] @@ -18,8 +16,12 @@ [setting :as setting] [user :as user :refer [User]] [view-log :refer [ViewLog]]] - [metabase.util.i18n :refer [trs]] - [toucan.db :as db])) + [metabase.util + [date-2 :as u.date] + [i18n :refer [trs]]] + [schema.core :as s] + [toucan.db :as db]) + (:import java.time.temporal.Temporal)) ;;; +----------------------------------------------------------------------------------------------------------------+ ;;; | send follow-up emails | @@ -51,7 +53,7 @@ (defn- instance-creation-timestamp "The date this Metabase instance was created. We use the `:date_joined` of the first `User` to determine this." - ^java.sql.Timestamp [] + ^java.time.temporal.Temporal [] (db/select-one-field :date_joined User, {:order-by [[:date_joined :asc]]})) ;; this sends out a general 2 week email follow up email @@ -60,9 +62,8 @@ (when-not (follow-up-email-sent) ;; figure out when we consider the instance created (when-let [instance-created (instance-creation-timestamp)] - ;; we need to be 2+ weeks (14 days) from creation to send the follow up - (when (< (* 14 24 60 60 1000) - (- (System/currentTimeMillis) (.getTime instance-created))) + ;; we need to be 2+ weeks from creation to send the follow up + (when (u.date/older-than? instance-created (t/weeks 2)) (send-follow-up-email!))))) (def ^:private follow-up-emails-job-key "metabase.task.follow-up-emails.job") @@ -91,39 +92,47 @@ :default false :internal? true) -(defn- send-abandonment-email! +(s/defn ^:private should-send-abandoment-email? + ([] + (should-send-abandoment-email? + (instance-creation-timestamp) + (db/select-one [User [:%max.date_joined :last-user]]) + (db/select-one [Activity [:%max.timestamp :last-activity]]) + (db/select-one [ViewLog [:%max.timestamp :last-view]]))) + + ([instance-creation :- (s/maybe Temporal) + last-user :- (s/maybe Temporal) + last-activity :- (s/maybe Temporal) + last-view :- (s/maybe Temporal)] + (boolean + (and instance-creation + (u.date/older-than? instance-creation (t/weeks 4)) + (or (not last-user) (u.date/older-than? last-user (t/weeks 2))) + (or (not last-activity) (u.date/older-than? last-activity (t/weeks 2))) + (or (not last-view) (u.date/older-than? last-view (t/weeks 2))))))) + +(defn- send-abandoment-email-if-needed! "Send an email to the instance admin about why Metabase usage has died down." [] ;; grab the oldest admins email address, that's who we'll send to - (when-let [admin (User :is_superuser true, {:order-by [:date_joined]})] - ;; inactive = no users created, no activity created, no dash/card views (past 7 days) - (let [last-user (c/from-sql-time (db/select-one-field :date_joined User, {:order-by [[:date_joined :desc]]})) - last-activity (c/from-sql-time (db/select-one-field :timestamp Activity, {:order-by [[:timestamp :desc]]})) - last-view (c/from-sql-time (db/select-one-field :timestamp ViewLog, {:order-by [[:timestamp :desc]]})) - two-weeks-ago (t/minus (t/now) (t/days 14))] - (when (and (t/before? last-user two-weeks-ago) - (t/before? last-activity two-weeks-ago) - (t/before? last-view two-weeks-ago)) - (try - (messages/send-follow-up-email! (:email admin) "abandon") - (catch Throwable e - (log/error e (trs "Problem sending abandonment email"))) - (finally - (abandonment-email-sent true))))))) + (when-let [admin-email (db/select-one-field :email User :is_superuser true, {:order-by [:date_joined]})] + (when (should-send-abandoment-email?) + (log/info (trs "Sending abandoment email!")) + (try + (messages/send-follow-up-email! admin-email "abandon") + (catch Throwable e + (log/error e (trs "Problem sending abandonment email"))) + (finally + (abandonment-email-sent true)))))) ;; this sends out an email any time after 30 days if the instance has stopped being used for 14 days (jobs/defjob AbandonmentEmail [_] ;; if we've already sent the abandonment email then we are done (when-not (abandonment-email-sent) - ;; figure out when we consider the instance created - (when-let [instance-created (instance-creation-timestamp)] - ;; we need to be 4+ weeks (30 days) from creation to send the follow up - (when (< (* 30 24 60 60 1000) - (- (System/currentTimeMillis) (.getTime instance-created))) - ;; we need access to email AND the instance must be opted into anonymous tracking - (when (and (email/email-configured?) - (public-settings/anon-tracking-enabled)) - (send-abandonment-email!)))))) + ;; we need access to email AND the instance must be opted into anonymous tracking + (when (and (email/email-configured?) + (public-settings/anon-tracking-enabled)) + (send-abandoment-email-if-needed!)))) (def ^:private abandonment-emails-job-key "metabase.task.abandonment-emails.job") (def ^:private abandonment-emails-trigger-key "metabase.task.abandonment-emails.trigger") diff --git a/src/metabase/util.clj b/src/metabase/util.clj index 5ec4add1925..da4efc2cbdb 100644 --- a/src/metabase/util.clj +++ b/src/metabase/util.clj @@ -18,8 +18,7 @@ [metabase.util.i18n :refer [trs tru]] [ring.util.codec :as codec] [weavejester.dependency :as dep]) - (:import [java.io BufferedReader Reader] - [java.net InetAddress InetSocketAddress Socket] + (:import [java.net InetAddress InetSocketAddress Socket] [java.text Normalizer Normalizer$Form] java.util.concurrent.TimeoutException java.util.Locale @@ -59,43 +58,6 @@ [& body] `(try ~@body (catch Throwable ~'_))) -;;; ## Etc - -(defprotocol ^:private ^:deprecated IClobToStr - (^:deprecated jdbc-clob->str ^String [this] - "Convert a Postgres/H2/SQLServer JDBC Clob to a string. (If object isn't a Clob, this function returns it as-is.) - DEPRECATED — we should convert CLOBS to strings as they're read out of the database, instead of doing it after the - fact.")) - -(extend-protocol IClobToStr - nil (jdbc-clob->str [_] nil) - Object (jdbc-clob->str [this] this) - - org.postgresql.util.PGobject - (jdbc-clob->str [this] (.getValue this)) - - ;; H2 + SQLServer clobs both have methods called `.getCharacterStream` that officially return a `Reader`, - ;; but in practice I've only seen them return a `BufferedReader`. Just to be safe include a method to convert - ;; a plain `Reader` to a `BufferedReader` so we don't get caught with our pants down - Reader - (jdbc-clob->str [this] - (jdbc-clob->str (BufferedReader. this))) - - ;; Read all the lines for the `BufferedReader` and combine into a single `String` - BufferedReader - (jdbc-clob->str [this] - (with-open [_ this] - (loop [acc []] - (if-let [line (.readLine this)] - (recur (conj acc line)) - (str/join "\n" acc))))) - - ;; H2 -- See also http://h2database.com/javadoc/org/h2/jdbc/JdbcClob.html - org.h2.jdbc.JdbcClob - (jdbc-clob->str [this] - (jdbc-clob->str (.getCharacterStream this)))) - - (defn optional "Helper function for defining functions that accept optional arguments. If `pred?` is true of the first item in `args`, a pair like `[first-arg other-args]` is returned; otherwise, a pair like `[default other-args]` is returned. @@ -316,6 +278,7 @@ (some->> last-mb-frame (str "--> ")) frames-before-last-mb))))}) +(declare format-milliseconds) (defn deref-with-timeout "Call `deref` on a something derefable (e.g. a future or promise), and throw an exception if it takes more than @@ -325,7 +288,7 @@ (when (= result ::timeout) (when (instance? java.util.concurrent.Future reff) (future-cancel reff)) - (throw (TimeoutException. (tru "Timed out after {0} milliseconds." timeout-ms)))) + (throw (TimeoutException. (tru "Timed out after {0}" (format-milliseconds timeout-ms))))) result)) (defn do-with-timeout @@ -791,4 +754,62 @@ "Changes the keys of a given map to lower case." [m] (into {} (for [[k v] m] - [(-> k name lower-case-en keyword) v]))) \ No newline at end of file + [(-> k name lower-case-en keyword) v]))) + +(defn format-nanoseconds + "Format a time interval in nanoseconds to something more readable. (µs/ms/etc.)" + ^String [nanoseconds] + ;; The basic idea is to take `n` and see if it's greater than the divisior. If it is, we'll print it out as that + ;; unit. If more, we'll divide by the divisor and recur, trying each successively larger unit in turn. e.g. + ;; + ;; (format-nanoseconds 500) ; -> "500 ns" + ;; (format-nanoseconds 500000) ; -> "500 µs" + (loop [n nanoseconds, [[unit divisor] & more] [[:ns 1000] [:µs 1000] [:ms 1000] [:s 60] [:mins 60] [:hours 24] + [:days 7] [:weeks (/ 365.25 7)] [:years Double/POSITIVE_INFINITY]]] + (if (and (> n divisor) + (seq more)) + (recur (/ n divisor) more) + (format "%.1f %s" (double n) (name unit))))) + +(defn format-microseconds + "Format a time interval in microseconds into something more readable." + ^String [microseconds] + (format-nanoseconds (* 1000.0 microseconds))) + +(defn format-milliseconds + "Format a time interval in milliseconds into something more readable." + ^String [milliseconds] + (format-microseconds (* 1000.0 milliseconds))) + +(defn format-seconds + "Format a time interval in seconds into something more readable." + ^String [seconds] + (format-milliseconds (* 1000.0 seconds))) + +(defmacro profile + "Like `clojure.core/time`, but lets you specify a `message` that gets printed with the total time, and formats the + time nicely using `format-nanoseconds`." + {:style/indent 1} + ([form] + `(profile ~(str form) ~form)) + ([message & body] + `(let [start-time# (System/nanoTime)] + (u/prog1 (do ~@body) + (println (u/format-color '~'green "%s took %s" + ~message + (format-nanoseconds (- (System/nanoTime) start-time#)))))))) + +(defn seconds->ms + "Convert `seconds` to milliseconds. More readable than doing this math inline." + [seconds] + (* seconds 1000)) + +(defn minutes->seconds + "Convert `minutes` to seconds. More readable than doing this math inline." + [minutes] + (* 60 minutes)) + +(defn minutes->ms + "Convert `minutes` to milliseconds. More readable than doing this math inline." + [minutes] + (-> minutes minutes->seconds seconds->ms)) diff --git a/src/metabase/util/date.clj b/src/metabase/util/date.clj deleted file mode 100644 index 19b180a0fdb..00000000000 --- a/src/metabase/util/date.clj +++ /dev/null @@ -1,509 +0,0 @@ -(ns ^:deprecated metabase.util.date - "Utility functions for working with datetimes of different types, and other related tasks. Deprecated: use - `metabase.util.date-2` instead. - - TIMEZONE FIXME - remove this namespace entirely -" - (:require [clj-time - [coerce :as coerce] - [core :as t] - [format :as time]] - [clojure.math.numeric-tower :as math] - [clojure.tools.logging :as log] - [metabase.util :as u] - [metabase.util - [i18n :refer [deferred-trs]] - [schema :as su]] - [schema.core :as s]) - (:import clojure.lang.Keyword - [java.sql Time Timestamp] - [java.util Calendar Date TimeZone] - [org.joda.time DateTime DateTimeZone] - org.joda.time.format.DateTimeFormatter)) - -(alter-meta! *ns* assoc :deprecated true) - -;; TODO - move this stuff to `metabase.query-processor.timestamp` -- it's QP specific and doesn't really belong here -(def ^:dynamic ^:deprecated ^TimeZone ^{:doc "Timezone to be used when formatting timestamps for display or - for the data (pre aggregation). Deprecated — use `metabase.query-processor.timezone/results-timezone-id` instead."} - *report-timezone*) - -(def ^:dynamic ^:deprecated ^TimeZone ^{:doc "The timezone of the database currently being queried. - Deprecated — you shouldn't need to use this directly; use `metabase.query-processor.timezone/results-timezone-id` - instead."} *database-timezone*) - -(defprotocol ^:private ITimeZoneCoercible - "Coerce object to `java.util.TimeZone`" - (coerce-to-timezone ^TimeZone [this] - "Coerce `this` to `java.util.TimeZone`")) - -(extend-protocol ^:private ITimeZoneCoercible - String (coerce-to-timezone [this] - (TimeZone/getTimeZone this)) - TimeZone (coerce-to-timezone [this] - this) - DateTimeZone (coerce-to-timezone [this] - (.toTimeZone this))) - -(def ^TimeZone utc - "UTC TimeZone" - (coerce-to-timezone "UTC")) - -(def jvm-timezone - "Machine time zone" - (delay (coerce-to-timezone (System/getProperty "user.timezone")))) - -(defn- warn-on-timezone-conflict - "Attempts to check the combination of report-timezone, jvm-timezone and database-timezone to determine of we have a - possible conflict. If one is found, warn the user." - [driver db ^TimeZone report-timezone ^TimeZone jvm-timezone ^TimeZone database-timezone] - ;; No need to check this if we don't have a database-timezone - (when (and database-timezone driver) - (let [jvm-data-tz-conflict? (not (.hasSameRules jvm-timezone database-timezone))] - (if ((resolve 'metabase.driver/supports?) driver :set-timezone) - ;; This database could have a report-timezone configured, if it doesn't and the JVM and data timezones don't - ;; match, we should suggest that the user configure a report timezone - (when (and (not report-timezone) - jvm-data-tz-conflict?) - (log/warn (str (deferred-trs "Possible timezone conflict found on database {0}." (:name db)) - " " - (deferred-trs "JVM timezone is {0} and detected database timezone is {1}." - (.getID jvm-timezone) (.getID database-timezone)) - " " - (deferred-trs "Configure a report timezone to ensure proper date and time conversions.")))) - ;; This database doesn't support a report timezone, check the JVM and data timezones, if they don't match, - ;; warn the user - (when jvm-data-tz-conflict? - (log/warn (str (deferred-trs "Possible timezone conflict found on database {0}." (:name db)) - " " - (deferred-trs "JVM timezone is {0} and detected database timezone is {1}." - (.getID jvm-timezone) (.getID database-timezone))))))))) - -(defn call-with-effective-timezone - "Invokes `f` with `*report-timezone*` and `*database-timezone*` bound for the given `db`. Deprecated — use functions - in `metabase.query-processor.timezone` instead." - [db f] - (let [driver ((resolve 'metabase.driver.util/database->driver) db) - report-tz (when-let [report-tz-id (and driver ((resolve 'metabase.driver.util/report-timezone-if-supported) driver))] - (coerce-to-timezone report-tz-id)) - data-tz (some-> db :timezone coerce-to-timezone) - jvm-tz @jvm-timezone] - (warn-on-timezone-conflict driver db report-tz jvm-tz data-tz) - (binding [*report-timezone* (or report-tz jvm-tz) - *database-timezone* data-tz] - (f)))) - -(defmacro with-effective-timezone - "Runs `body` with `*report-timezone*` and `*database-timezone*` configured using the given `db`. Deprecated — use - functions in `metabase.query-processor.timezone` instead." - [db & body] - `(call-with-effective-timezone ~db (fn [] ~@body))) - -(defprotocol ^:private ITimestampCoercible - "Coerce object to a `java.sql.Timestamp`." - (coerce-to-timestamp ^java.sql.Timestamp [this] [this timezone-coercible] - "Coerce this object to a `java.sql.Timestamp`. Strings are parsed as ISO-8601.")) - -(extend-protocol ^:private ITimestampCoercible - nil (coerce-to-timestamp [_] - nil) - Timestamp (coerce-to-timestamp [this] - this) - Date (coerce-to-timestamp - [this] - (coerce/to-timestamp (coerce/from-date this))) - ;; Number is assumed to be a UNIX timezone in milliseconds (UTC) - Number (coerce-to-timestamp [this] - (coerce/to-timestamp (coerce/from-long (long this)))) - Calendar (coerce-to-timestamp [this] - (coerce-to-timestamp (.getTime this))) - DateTime (coerce-to-timestamp [this] - (coerce/to-timestamp this))) - -(declare str->date-time) - -(defn ^Timestamp ->Timestamp - "Converts `coercible-to-ts` to a `java.util.Timestamp`. Requires a `coercible-to-tz` if converting a string. Leans - on clj-time to ensure correct conversions between the various types - - NOTE: This function requires you to pass in a timezone or bind `*report-timezone*`, probably to make sure you're not - doing something dumb by forgetting it. For cases where you'd just like to parse an ISO-8601-encoded String in peace - without specifying a timezone, pass in `:no-timezone` as the second param to explicitly have things parsed without - one. (Keep in mind that if your string does not specify a timezone, it will be parsed as UTC by default.)" - ([coercible-to-ts] - {:pre [(or (not (string? coercible-to-ts)) - (and (string? coercible-to-ts) (bound? #'*report-timezone*)))]} - (->Timestamp coercible-to-ts *report-timezone*)) - ([coercible-to-ts timezone] - {:pre [(or (not (string? coercible-to-ts)) - (and (string? coercible-to-ts) timezone))]} - (if (string? coercible-to-ts) - (coerce-to-timestamp (str->date-time coercible-to-ts (when-not (= timezone :no-timezone) - (coerce-to-timezone timezone)))) - (coerce-to-timestamp coercible-to-ts)))) - -(defprotocol ^:private IDateTimeFormatterCoercible - "Protocol for converting objects to `DateTimeFormatters`." - (->DateTimeFormatter ^org.joda.time.format.DateTimeFormatter [this] - "Coerce object to a `DateTimeFormatter`.")) - -(extend-protocol IDateTimeFormatterCoercible - ;; Specify a format string like "yyyy-MM-dd" - String (->DateTimeFormatter [this] (time/formatter this)) - DateTimeFormatter (->DateTimeFormatter [this] this) - ;; Keyword will be used to get matching formatter from time/formatters - Keyword (->DateTimeFormatter [this] - (or (time/formatters this) - (throw (Exception. (format "Invalid formatter name, must be one of:\n%s" - (u/pprint-to-str (sort (keys time/formatters))))))))) - - -(defn parse-date - "Parse a datetime string `s` with a custom `date-format`, which can be a format string, clj-time formatter keyword, or - anything else that can be coerced to a `DateTimeFormatter`. - - (parse-date \"yyyyMMdd\" \"20160201\") -> #inst \"2016-02-01\" - (parse-date :date-time \"2016-02-01T00:00:00.000Z\") -> #inst \"2016-02-01\"" - ^java.sql.Timestamp [date-format, ^String s] - (->Timestamp (time/parse (->DateTimeFormatter date-format) s))) - -(defprotocol ^:private ISO8601 - "Protocol for converting objects to ISO8601 formatted strings." - (->iso-8601-datetime ^String [this, ^String timezone-id-or-nil] - "Coerce object to an ISO8601 date-time string such as \"2015-11-18T23:55:03.841Z\" with a given `timezone-id` - string (such as '\"UTC\"'), or `nil`, which defaults to \"UTC\" (?)")) - -(def ^:private ^{:arglists '([timezone-id])} ISO8601Formatter - ;; memoize this because the formatters are static. They must be distinct per timezone though. - (memoize - (fn [^String timezone-id] - (if timezone-id - (time/with-zone (time/formatters :date-time) (t/time-zone-for-id timezone-id)) - (time/formatters :date-time))))) - -(extend-protocol ISO8601 - nil (->iso-8601-datetime [_ _] nil) - java.util.Date (->iso-8601-datetime [this timezone-id] (time/unparse (ISO8601Formatter timezone-id) (coerce/from-date this))) - java.sql.Date (->iso-8601-datetime [this timezone-id] (time/unparse (ISO8601Formatter timezone-id) (coerce/from-sql-date this))) - java.sql.Timestamp (->iso-8601-datetime [this timezone-id] (time/unparse (ISO8601Formatter timezone-id) (coerce/from-sql-time this))) - org.joda.time.DateTime (->iso-8601-datetime [this timezone-id] (time/unparse (ISO8601Formatter timezone-id) this))) - -(def ^:private ^{:arglists '([timezone-id])} time-formatter - ;; memoize this because the formatters are static. They must be distinct per timezone though. - (memoize - (fn [timezone-id] - (if timezone-id - (time/with-zone (time/formatters :time) (t/time-zone-for-id timezone-id)) - (time/formatters :time))))) - -(defn format-time - "Returns a string representation of the time found in `t`" - [t time-zone-id] - (time/unparse (time-formatter time-zone-id) (coerce/to-date-time t))) - -(defn is-time? - "Returns true if `v` is a Time object" - [v] - (and v (instance? Time v))) - -;;; ## Date Stuff - -(defn is-temporal? - "Is VALUE an instance of a datetime class like `java.util.Date` or `org.joda.time.DateTime`?" - [v] - (or (instance? java.util.Date v) - (instance? org.joda.time.DateTime v))) - -(defn new-sql-timestamp - "`java.sql.Date` doesn't have an empty constructor so this is a convenience that lets you make one with the current - date. (Some DBs like Postgres will get snippy if you don't use a `java.sql.Timestamp`)." - ^java.sql.Timestamp [] - (->Timestamp (System/currentTimeMillis))) - -(defn format-date - "Format `date` using a given `date-format`. NOTE: This will create a date string in the JVM's timezone, not the report - timezone. - - `date` is anything that can coerced to a `Timestamp` via `->Timestamp`, such as a `Date`, `Timestamp`, - `Long` (ms since the epoch), or an ISO-8601 `String`. `date` defaults to the current moment in time. - - `date-format` is anything that can be passed to `->DateTimeFormatter`, such as `String` - (using [the usual date format args](http://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html)), - `Keyword`, or `DateTimeFormatter`. - - - (format-date \"yyyy-MM-dd\") -> \"2015-11-18\" - (format-date :year (java.util.Date.)) -> \"2015\" - (format-date :date-time (System/currentTimeMillis)) -> \"2015-11-18T23:55:03.841Z\"" - (^String [date-format] - (format-date date-format (System/currentTimeMillis))) - (^String [date-format date] - (time/unparse (->DateTimeFormatter date-format) (coerce/from-sql-time (->Timestamp date))))) - -(def ^{:arglists '([] [date])} date->iso-8601 - "Format `date` a an ISO-8601 string." - (partial format-date :date-time)) - -(defn date-string? - "Is S a valid ISO 8601 date string?" - [^String s] - (boolean (when (string? s) - (u/ignore-exceptions - ;; Using UTC as the timezone here as it's `def`'d and the result of the parse is discarded, any - ;; timezone is fine here - (->Timestamp s utc))))) - -(defn ->Date - "Coerece `date` to a `java.util.Date`." - (^java.util.Date [] - (java.util.Date.)) - (^java.util.Date [date] - (java.util.Date. (.getTime (->Timestamp date))))) - -(defn ->Calendar - "Coerce `date` to a `java.util.Calendar`." - (^java.util.Calendar [] - (doto (Calendar/getInstance) - (.setTimeZone (TimeZone/getTimeZone "UTC")))) - (^java.util.Calendar [date] - (doto (->Calendar) - (.setTime (->Timestamp date)))) - (^java.util.Calendar [date, ^String timezone-id] - (doto (->Calendar date) - (.setTimeZone (TimeZone/getTimeZone timezone-id))))) - -(defn relative-date - "Return a new Timestamp relative to the current time using a relative date `unit`. - - (relative-date :year -1) -> #inst 2014-11-12 ..." - (^java.sql.Timestamp [unit amount] - (relative-date unit amount (Calendar/getInstance))) - (^java.sql.Timestamp [unit amount date] - (let [cal (->Calendar date) - [unit multiplier] (case unit - :millisecond [Calendar/MILLISECOND 1] - :second [Calendar/SECOND 1] - :minute [Calendar/MINUTE 1] - :hour [Calendar/HOUR 1] - :day [Calendar/DATE 1] - :week [Calendar/DATE 7] - :month [Calendar/MONTH 1] - :quarter [Calendar/MONTH 3] - :year [Calendar/YEAR 1])] - (.set cal unit (+ (.get cal unit) - (* amount multiplier))) - (->Timestamp cal)))) - -(def date-extract-units - "Units which return a (numerical, periodic) component of a date" - #{:minute-of-hour :hour-of-day :day-of-week :day-of-month :day-of-year :week-of-year :month-of-year :quarter-of-year - :year}) - -(defn date-extract - "Extract `unit` from `date`. `date` defaults to now. - - (date-extract :year) -> 2015" - ([unit] - (date-extract unit (System/currentTimeMillis) "UTC")) - ([unit date] - (date-extract unit date "UTC")) - ([unit date timezone-id] - (let [cal (->Calendar date timezone-id)] - (case unit - :minute-of-hour (.get cal Calendar/MINUTE) - :hour-of-day (.get cal Calendar/HOUR_OF_DAY) - ;; 1 = Sunday <-> 6 = Saturday - :day-of-week (.get cal Calendar/DAY_OF_WEEK) - :day-of-month (.get cal Calendar/DAY_OF_MONTH) - :day-of-year (.get cal Calendar/DAY_OF_YEAR) - ;; 1 = First week of year - :week-of-year (.get cal Calendar/WEEK_OF_YEAR) - :month-of-year (inc (.get cal Calendar/MONTH)) - :quarter-of-year (let [month (date-extract :month-of-year date timezone-id)] - (int (/ (+ 2 month) - 3))) - :year (.get cal Calendar/YEAR))))) - -(def date-trunc-units - "Valid date bucketing units" - #{:second :minute :hour :day :week :month :quarter :year}) - -(defn- trunc-with-format [format-string date timezone-id] - (->Timestamp (format-date (time/with-zone (time/formatter format-string) - (t/time-zone-for-id timezone-id)) - date) - timezone-id)) - -(defn- trunc-with-floor [date amount-ms] - (->Timestamp (* (math/floor (/ (.getTime (->Timestamp date)) - amount-ms)) - amount-ms))) - -(defn- ->first-day-of-week [date timezone-id] - (let [day-of-week (date-extract :day-of-week date timezone-id)] - (relative-date :day (- (dec day-of-week)) date))) - -(defn- format-string-for-quarter ^String [date timezone-id] - (let [year (date-extract :year date timezone-id) - quarter (date-extract :quarter-of-year date timezone-id) - month (- (* 3 quarter) 2)] - (format "%d-%02d-01'T'ZZ" year month))) - -(defn date-trunc - "Truncate `date` to `unit`. `date` defaults to now. - - (date-trunc :month). - ;; -> #inst \"2015-11-01T00:00:00\"" - (^java.sql.Timestamp [unit] - (date-trunc unit (System/currentTimeMillis) "UTC")) - - (^java.sql.Timestamp [unit date] - (date-trunc unit date "UTC")) - - (^java.sql.Timestamp [unit date timezone-id] - (case unit - ;; For minute and hour truncation timezone should not be taken into account - :millisecond (trunc-with-floor date 1) - :second (trunc-with-floor date 1000) - :minute (trunc-with-floor date (* 60 1000)) - :hour (trunc-with-floor date (* 60 60 1000)) - :day (trunc-with-format "yyyy-MM-dd'T'ZZ" date timezone-id) - :week (trunc-with-format "yyyy-MM-dd'T'ZZ" (->first-day-of-week date timezone-id) timezone-id) - :month (trunc-with-format "yyyy-MM-01'T'ZZ" date timezone-id) - :quarter (trunc-with-format (format-string-for-quarter date timezone-id) date timezone-id) - :year (trunc-with-format "yyyy-01-01'T'ZZ" date timezone-id)))) - -(defn date-trunc-or-extract - "Apply date bucketing with `unit` to `date`. `date` defaults to now." - ([unit] - (date-trunc-or-extract unit (System/currentTimeMillis) "UTC")) - ([unit date] - (date-trunc-or-extract unit date "UTC")) - ([unit date timezone-id] - (cond - (= unit :default) date - - (contains? date-extract-units unit) - (date-extract unit date timezone-id) - - (contains? date-trunc-units unit) - (date-trunc unit date timezone-id)))) - -(defn format-nanoseconds - "Format a time interval in nanoseconds to something more readable (µs/ms/etc.) - Useful for logging elapsed time when using `(System/nanotime)`" - ^String [nanoseconds] - (loop [n nanoseconds, [[unit divisor] & more] [[:ns 1000] [:µs 1000] [:ms 1000] [:s 60] [:mins 60] [:hours 24] - [:days 7] [:weeks Integer/MAX_VALUE]]] - (if (and (> n divisor) - (seq more)) - (recur (/ n divisor) more) - (format "%.1f %s" (double n) (name unit))))) - -(defn format-microseconds - "Format a time interval in microseconds into something more readable." - ^String [microseconds] - (format-nanoseconds (* 1000.0 microseconds))) - -(defn format-milliseconds - "Format a time interval in milliseconds into something more readable." - ^String [milliseconds] - (format-microseconds (* 1000.0 milliseconds))) - -(defn format-seconds - "Format a time interval in seconds into something more readable." - ^String [seconds] - (format-milliseconds (* 1000.0 seconds))) - -;; TODO - Not sure this belongs in the datetime util namespace -(defmacro profile - "Like `clojure.core/time`, but lets you specify a `message` that gets printed with the total time, and formats the - time nicely using `format-nanoseconds`." - {:style/indent 1} - ([form] - `(profile ~(str form) ~form)) - ([message & body] - `(let [start-time# (System/nanoTime)] - (u/prog1 (do ~@body) - (println (u/format-color '~'green "%s took %s" - ~message - (format-nanoseconds (- (System/nanoTime) start-time#)))))))) - -(defn- str->date-time-with-formatters - "Attempt to parse `date-str` using `formatters`. First successful parse is returned, or `nil` if it cannot be - successfully parsed." - ([formatters date-str] - (str->date-time-with-formatters formatters date-str nil)) - ([formatters ^String date-str ^TimeZone tz] - (let [dtz (some-> tz .getID t/time-zone-for-id)] - (first - (for [formatter formatters - :let [formatter-with-tz (time/with-zone formatter dtz) - parsed-date (u/ignore-exceptions (time/parse formatter-with-tz date-str))] - :when parsed-date] - parsed-date))))) - -(def ^:private date-time-with-millis-no-t - "This primary use for this formatter is for Dates formatted by the built-in SQLite functions" - (->DateTimeFormatter "yyyy-MM-dd HH:mm:ss.SSS")) - -(def ^:private ordered-date-parsers - "When using clj-time.format/parse without a formatter, it tries all default formatters, but not ordered by how likely - the date formatters will succeed. This leads to very slow parsing as many attempts fail before the right one is - found. Using this retains that flexibility but improves performance by trying the most likely ones first" - (let [most-likely-default-formatters [:mysql :date-hour-minute-second :date-time :date - :basic-date-time :basic-date-time-no-ms - :date-time :date-time-no-ms]] - (concat (map time/formatters most-likely-default-formatters) - [date-time-with-millis-no-t] - (vals (apply dissoc time/formatters most-likely-default-formatters))))) - -(defn str->date-time - "Like clj-time.format/parse but uses an ordered list of parsers to be faster. Returns the parsed date, or `nil` if it - was unable to be parsed." - (^DateTime [^String date-str] - (str->date-time date-str nil)) - - (^DateTime [^String date-str, ^TimeZone tz] - (str->date-time-with-formatters ordered-date-parsers date-str tz))) - -(def ^:private ordered-time-parsers - (let [most-likely-default-formatters [:hour-minute :hour-minute-second :hour-minute-second-fraction]] - (concat (map time/formatters most-likely-default-formatters) - [(time/formatter "HH:mmZ") (time/formatter "HH:mm:SSZ") (time/formatter "HH:mm:SS.SSSZ")]))) - -(defn str->time - "Parse `time-str` and return a `java.sql.Time` instance. Returns `nil` if `time-str` can't be parsed." - ([^String date-str] - (str->time date-str nil)) - ([^String date-str ^TimeZone tz] - (some-> (str->date-time-with-formatters ordered-time-parsers date-str tz) - coerce/to-long - Time.))) - -(s/defn calculate-duration :- su/NonNegativeInt - "Given two datetimes, caculate the time between them, return the result in millis" - [begin-time :- (s/protocol coerce/ICoerce) - end-time :- (s/protocol coerce/ICoerce)] - (- (coerce/to-long end-time) (coerce/to-long begin-time))) - -(defn seconds->ms - "Convert `seconds` to milliseconds. More readable than doing this math inline." - [seconds] - (* seconds 1000)) - -(defn minutes->seconds - "Convert `minutes` to seconds. More readable than doing this math inline." - [minutes] - (* 60 minutes)) - -(defn minutes->ms - "Convert `minutes` to milliseconds. More readable than doing this math inline." - [minutes] - (-> minutes minutes->seconds seconds->ms)) - -;; deprecate this entire namespace and `clj-time` as well! -(doseq [a-namespace (cons *ns* '[clj-time.core clj-time.coerce clj-time.format])] - (alter-meta! (the-ns a-namespace) assoc :deprecated true) - (doseq [[_ varr] (ns-publics a-namespace)] - (alter-meta! varr assoc :deprecated true))) diff --git a/src/metabase/util/date_2.clj b/src/metabase/util/date_2.clj index 375de48b029..08a18341353 100644 --- a/src/metabase/util/date_2.clj +++ b/src/metabase/util/date_2.clj @@ -11,8 +11,9 @@ [parse :as parse]] [metabase.util.i18n :refer [tru]] [schema.core :as s]) - (:import [java.time Instant LocalDate LocalDateTime LocalTime OffsetDateTime OffsetTime ZonedDateTime] - [java.time.temporal Temporal TemporalAdjuster WeekFields])) + (:import [java.time Duration Instant LocalDate LocalDateTime LocalTime OffsetDateTime OffsetTime Period ZonedDateTime] + [java.time.temporal Temporal TemporalAdjuster WeekFields] + org.threeten.extra.PeriodDuration)) (defn- add-zone-to-local [t timezone-id] (condp instance? t @@ -45,6 +46,7 @@ (defn- temporal->iso-8601-formatter [t] (condp instance? t + Instant :iso-offset-date-time LocalDate :iso-local-date LocalTime :iso-local-time LocalDateTime :iso-local-date-time @@ -281,27 +283,88 @@ :exclusive (add t resolution -1)))} := (range t unit options)))) +(defn ^PeriodDuration period-duration + "Return the Duration between two temporal values `x` and `y`." + {:arglists '([s] [period] [duration] [period duration] [start end])} + ([x] + (when x + (condp instance? x + PeriodDuration x + CharSequence (PeriodDuration/parse x) + Period (PeriodDuration/of ^Period x) + Duration (PeriodDuration/of ^Duration x)))) + + ([x y] + (cond + (and (instance? Period x) (instance? Duration y)) + (PeriodDuration/of x y) -;;; +----------------------------------------------------------------------------------------------------------------+ -;;; | Etc | -;;; +----------------------------------------------------------------------------------------------------------------+ + (instance? Instant x) + (period-duration (t/offset-date-time x (t/zone-offset 0)) y) + + (instance? Instant y) + (period-duration x (t/offset-date-time y (t/zone-offset 0))) + + :else + (PeriodDuration/between x y)))) + +(defn compare-period-durations + "With two args: Compare two periods/durations. Returns a negative value if `d1` is shorter than `d2`, zero if they are + equal, or positive if `d1` is longer than `d2`. + + (u.date/compare-period-durations \"P1Y\" \"P11M\") ; -> 1 (i.e., 1 year is longer than 11 months) -;; TIMEZONE FIXME - I think these actually belong in `metabase.util` + You can combine this with `period-duration` to compare the duration between two temporal values against another + duration: -(defn seconds->ms - "Convert `seconds` to milliseconds. More readable than doing this math inline." - [seconds] - (* seconds 1000)) + (u.date/compare-period-durations (u.date/period-duration #t \"2019-01-01\" #t \"2019-07-01\") \"P11M\") ; -> -1 -(defn minutes->seconds - "Convert `minutes` to seconds. More readable than doing this math inline." - [minutes] - (* 60 minutes)) + Note that this calculation is inexact, since it calclates relative to a fixed point in time, but should be + sufficient for most if not all use cases." + [d1 d2] + (when (and d1 d2) + (let [t (t/offset-date-time "1970-01-01T00:00Z")] + (compare (.addTo (period-duration d1) t) + (.addTo (period-duration d2) t))))) -(defn minutes->ms - "Convert `minutes` to milliseconds. More readable than doing this math inline." - [minutes] - (-> minutes minutes->seconds seconds->ms)) +(defn less-than-period-duration? + "True if period/duration `d1` is shorter than period/duration `d2`." + [d1 d2] + (neg? (compare-period-durations d1 d2))) + +(defn greater-than-period-duration? + "True if period/duration `d1` is longer than period/duration `d2`." + [d1 d2] + (pos? (compare-period-durations d1 d2))) + +(defn- now-of-same-class + "Return a temporal value representing *now* of the same class as `t`, e.g. for comparison purposes." + ^Temporal [t] + (when t + (condp instance? t + Instant (t/instant) + LocalDate (t/local-date) + LocalTime (t/local-time) + LocalDateTime (t/local-date-time) + OffsetTime (t/offset-time) + OffsetDateTime (t/offset-date-time) + ZonedDateTime (t/zoned-date-time)))) + +(defn older-than? + "True if temporal value `t` happened before some period/duration ago. Prefer this over using `t/before?` + because it is incredibly fussy about the classes of arguments it is passed. + + ;; did `t` happen more than 2 months ago? + (older-than? t (t/months 2))" + [t duration] + (greater-than-period-duration? + (period-duration t (now-of-same-class t)) + duration)) + + +;;; +----------------------------------------------------------------------------------------------------------------+ +;;; | Etc | +;;; +----------------------------------------------------------------------------------------------------------------+ ;; Mainly for REPL usage. Have various temporal types print as a `java-time` function call you can use (doseq [[klass f-symb] {Instant 't/instant @@ -311,6 +374,35 @@ OffsetDateTime 't/offset-date-time OffsetTime 't/offset-time ZonedDateTime 't/zoned-date-time}] -(defmethod print-method klass - [t writer] - (print-method (list f-symb (str t)) writer))) + (defmethod print-method klass + [t writer] + (print-method (list f-symb (str t)) writer)) + + (defmethod print-dup klass + [t ^java.io.Writer writer] + (.write writer (clojure.core/format "#t \"%s\"" (str t))))) + +(defmethod print-method PeriodDuration + [d writer] + (print-method (list 'u.date/period-duration (str d)) writer)) + +(defmethod print-dup PeriodDuration + [d ^java.io.Writer writer] + (.write writer (clojure.core/format "(metabase.util.date-2/period-duration \"%s\")" (str d)))) + +(defmethod print-method Period + [d writer] + (print-method (list 't/period (str d)) writer)) + +(defmethod print-method Duration + [d writer] + (print-method (list 't/duration (str d)) writer)) + +;; mark everything in the `clj-time` namespaces as `:deprecated`, if they're loaded. If not, we don't care +(doseq [a-namespace '[clj-time.core clj-time.coerce clj-time.format]] + (try + (let [a-namespace (the-ns a-namespace)] + (alter-meta! a-namespace assoc :deprecated true) + (doseq [[_ varr] (ns-publics a-namespace)] + (alter-meta! varr assoc :deprecated true))) + (catch Throwable _))) diff --git a/src/metabase/util/date_2/common.clj b/src/metabase/util/date_2/common.clj index 6dc21a635f8..c322c46aa7f 100644 --- a/src/metabase/util/date_2/common.clj +++ b/src/metabase/util/date_2/common.clj @@ -1,12 +1,34 @@ (ns metabase.util.date-2.common - (:require [clojure.string :as str])) + (:require [clojure.string :as str]) + (:import [java.time.temporal ChronoField IsoFields TemporalField WeekFields])) ;; TODO - not sure this belongs here, it seems to be a bit more general than just `date-2`. (defn static-instances "Utility function to get the static members of a class. Returns map of `lisp-case` keyword names of members -> value." - [^Class klass] - (into {} (for [^java.lang.reflect.Field f (.getFields klass) - :when (.isAssignableFrom klass (.getType f))] - [(keyword (str/lower-case (str/replace (.getName f) #"_" "-"))) - (.get f nil)]))) + ([^Class klass] + (static-instances klass klass)) + + ([^Class klass ^Class target-class] + (into {} (for [^java.lang.reflect.Field f (.getFields klass) + :when (.isAssignableFrom target-class (.getType f))] + [(keyword (str/lower-case (str/replace (.getName f) #"_" "-"))) + (.get f nil)])))) + +(def ^TemporalField temporal-field + "Map of lisp-style-name -> TemporalField for all the various TemporalFields we use in day-to-day parsing and other + temporal operations." + (merge + ;; honestly I have no idea why there's both IsoFields/WEEK_OF_WEEK_BASED_YEAR and (.weekOfWeekBasedYear + ;; WeekFields/ISO) + (into {} (for [[k v] (static-instances IsoFields TemporalField)] + [(keyword "iso" (name k)) v])) + (static-instances ChronoField) + {:week-fields/iso-week-based-year (.weekBasedYear WeekFields/ISO) + :week-fields/iso-week-of-month (.weekOfMonth WeekFields/ISO) + :week-fields/iso-week-of-week-based-year (.weekOfWeekBasedYear WeekFields/ISO) + :week-fields/iso-week-of-year (.weekOfYear WeekFields/ISO)} + {:week-fields/week-based-year (.weekBasedYear WeekFields/SUNDAY_START) + :week-fields/week-of-month (.weekOfMonth WeekFields/SUNDAY_START) + :week-fields/week-of-week-based-year (.weekOfWeekBasedYear WeekFields/SUNDAY_START) + :week-fields/week-of-year (.weekOfYear WeekFields/SUNDAY_START)})) diff --git a/src/metabase/util/date_2/parse.clj b/src/metabase/util/date_2/parse.clj index 614781d10c8..518312d1d81 100644 --- a/src/metabase/util/date_2/parse.clj +++ b/src/metabase/util/date_2/parse.clj @@ -1,10 +1,79 @@ (ns metabase.util.date-2.parse (:require [clojure.string :as str] [java-time :as t] - [metabase.util.date-2.parse.builder :as b]) + [metabase.util.date-2.common :as common] + [metabase.util.date-2.parse.builder :as b] + [metabase.util.i18n :refer [tru]] + [schema.core :as s]) (:import [java.time LocalDateTime OffsetDateTime OffsetTime ZonedDateTime ZoneOffset] java.time.format.DateTimeFormatter - [java.time.temporal TemporalAccessor TemporalQueries])) + [java.time.temporal Temporal TemporalAccessor TemporalField TemporalQueries])) + +(def ^:private ^{:arglists '([temporal-accessor query])} query + (let [queries {:local-date (TemporalQueries/localDate) + :local-time (TemporalQueries/localTime) + :zone-offset (TemporalQueries/offset) + :zone-id (TemporalQueries/zoneId)}] + (fn [^TemporalAccessor temporal-accessor query] + (.query temporal-accessor (queries query))))) + +(defn- normalize [s] + (-> s + ;; HACK - haven't figured out how to get the parser builder to allow HHmm offsets (i.e., no colons) yet, so add + ;; one in there if needed. TODO - what about HH:mm:ss offsets? Will we ever see those? + (str/replace #"([+-][0-2]\d)([0-5]\d)$" "$1:$2") + (str/replace #"([0-2]\d:[0-5]\d(?::[0-5]\d(?:\.\d{1,9})?)?[+-][0-2]\d$)" "$1:00"))) + +(defn all-supported-fields + "Returns a map of supported temporal field lisp-style name -> value, e.g. + + (parse-special-case (.parse + (b/formatter + (b/value :year 4) + (b/value :iso/week-of-year 2)) + \"201901\")) + ;; -> {:year 2019, :iso-week-of-year 1}" + [^TemporalAccessor temporal-accessor] + (into {} (for [[k ^TemporalField field] common/temporal-field + :when (.isSupported temporal-accessor field)] + [k (.getLong temporal-accessor field)]))) + +(s/defn parse-with-formatter :- (s/maybe Temporal) + "Parse a String with a DateTimeFormatter, returning an appropriate instance of an `java.time` temporal class." + [formattr s :- (s/maybe s/Str)] + {:pre [((some-fn string? nil?) s)]} + (when-not (str/blank? s) + (let [formattr (t/formatter formattr) + s (normalize s) + temporal-accessor (.parse formattr s) + local-date (query temporal-accessor :local-date) + local-time (query temporal-accessor :local-time) + zone-offset (query temporal-accessor :zone-offset) + zone-id (or (query temporal-accessor :zone-id) + (when (= zone-offset ZoneOffset/UTC) + (t/zone-id "UTC"))) + literal-type [(cond + zone-id :zone + zone-offset :offset + :else :local) + (cond + (and local-date local-time) :datetime + local-date :date + local-time :time)]] + (case literal-type + [:zone :datetime] (ZonedDateTime/of local-date local-time zone-id) + [:offset :datetime] (OffsetDateTime/of local-date local-time zone-offset) + [:local :datetime] (LocalDateTime/of local-date local-time) + [:zone :date] (ZonedDateTime/of local-date (t/local-time 0) zone-id) + [:offset :date] (OffsetDateTime/of local-date (t/local-time 0) zone-offset) + [:local :date] local-date + [:zone :time] (OffsetTime/of local-time zone-offset) + [:offset :time] (OffsetTime/of local-time zone-offset) + [:local :time] local-time + (throw (ex-info (tru "Don''t know how to parse {0} using format {1}" (pr-str s) (pr-str formattr)) + {:s s + :formatter formattr + :supported-fields (all-supported-fields temporal-accessor)})))))) (def ^:private ^DateTimeFormatter date-formatter* (b/formatter @@ -53,49 +122,7 @@ (b/optional offset-formatter*)))) -(def ^:private ^{:arglists '([temporal-accessor query])} query - (let [queries {:local-date (TemporalQueries/localDate) - :local-time (TemporalQueries/localTime) - :zone-offset (TemporalQueries/offset) - :zone-id (TemporalQueries/zoneId)}] - (fn [^TemporalAccessor temporal-accessor query] - (.query temporal-accessor (queries query))))) - -(defn- normalize [s] - (-> s - ;; HACK - haven't figured out how to get the parser builder to allow HHmm offsets (i.e., no colons) yet, so add - ;; one in there if needed. TODO - what about HH:mm:ss offsets? Will we ever see those? - (str/replace #"([+-][0-2]\d)([0-5]\d)$" "$1:$2") - (str/replace #"([0-2]\d:[0-5]\d(?::[0-5]\d(?:\.\d{1,9})?)?[+-][0-2]\d$)" "$1:00"))) - (defn parse "Parse a string into a `java.time` object." [^String s] - {:pre [((some-fn string? nil?) s)]} - (when-not (str/blank? s) - (let [s (normalize s) - temporal-accessor (.parse formatter s) - local-date (query temporal-accessor :local-date) - local-time (query temporal-accessor :local-time) - zone-offset (query temporal-accessor :zone-offset) - zone-id (or (query temporal-accessor :zone-id) - (when (= zone-offset ZoneOffset/UTC) - (t/zone-id "UTC"))) - literal-type [(cond - zone-id :zone - zone-offset :offset - :else :local) - (cond - (and local-date local-time) :datetime - local-date :date - local-time :time)]] - (case literal-type - [:zone :datetime] (ZonedDateTime/of local-date local-time zone-id) - [:offset :datetime] (OffsetDateTime/of local-date local-time zone-offset) - [:local :datetime] (LocalDateTime/of local-date local-time) - [:zone :date] (ZonedDateTime/of local-date (t/local-time 0) zone-id) - [:offset :date] (OffsetDateTime/of local-date (t/local-time 0) zone-offset) - [:local :date] local-date - [:zone :time] (OffsetTime/of local-time zone-offset) - [:offset :time] (OffsetTime/of local-time zone-offset) - [:local :time] local-time)))) + (parse-with-formatter formatter s)) diff --git a/src/metabase/util/date_2/parse/builder.clj b/src/metabase/util/date_2/parse/builder.clj index 14ef4c22cdf..b5550fdeb8e 100644 --- a/src/metabase/util/date_2/parse/builder.clj +++ b/src/metabase/util/date_2/parse/builder.clj @@ -9,8 +9,7 @@ TODO - this is a prime library candidate." (:require [metabase.util.date-2.common :as common]) - (:import [java.time.format DateTimeFormatter DateTimeFormatterBuilder SignStyle] - java.time.temporal.ChronoField)) + (:import [java.time.format DateTimeFormatter DateTimeFormatterBuilder SignStyle] java.time.temporal.TemporalField)) (defprotocol ^:private Section (^:private apply-section [this builder])) @@ -87,37 +86,42 @@ [& sections] (with-option-section :case-sensitivity :case-insensitive sections)) -(def ^:private ^ChronoField chrono-field - (common/static-instances ChronoField)) - (def ^:private ^SignStyle sign-style (common/static-instances SignStyle)) +(defn- temporal-field ^TemporalField [x] + (let [field (if (keyword? x) + (common/temporal-field x) + x)] + (assert (instance? TemporalField field) + (format "Invalid TemporalField: %s" (pr-str field))) + field)) + (defn value "Define a section for a specific field such as `:hour-of-day` or `:minute-of-hour`." - ([chrono-field-name] + ([temporal-field-name] (fn [^DateTimeFormatterBuilder builder] - (.appendValue builder (chrono-field chrono-field-name)))) + (.appendValue builder (temporal-field temporal-field-name)))) - ([chrono-field-name width] + ([temporal-field-name width] (fn [^DateTimeFormatterBuilder builder] - (.appendValue builder (chrono-field chrono-field-name) width))) + (.appendValue builder (temporal-field temporal-field-name) width))) - ([chrono-field-name min-val max-val sign-style-name] + ([temporal-field-name min-val max-val sign-style-name] (fn [^DateTimeFormatterBuilder builder] - (.appendValue builder (chrono-field chrono-field-name) min-val max-val (sign-style sign-style-name))))) + (.appendValue builder (temporal-field temporal-field-name) min-val max-val (sign-style sign-style-name))))) (defn default-value "Define a section that sets a default value for a field like `:minute-of-hour`." - [chrono-field-name default-value] + [temporal-field-name default-value] (fn [^DateTimeFormatterBuilder builder] - (.parseDefaulting builder (chrono-field chrono-field-name) default-value))) + (.parseDefaulting builder (temporal-field temporal-field-name) default-value))) (defn fraction "Define a section for a fractional value, e.g. milliseconds or nanoseconds." - [chrono-field-name min-val-width max-val-width & {:keys [decimal-point?]}] + [temporal-field-name min-val-width max-val-width & {:keys [decimal-point?]}] (fn [^DateTimeFormatterBuilder builder] - (.appendFraction builder (chrono-field chrono-field-name) 0 9 (boolean decimal-point?)))) + (.appendFraction builder (temporal-field temporal-field-name) 0 9 (boolean decimal-point?)))) (defn zone-offset "Define a section for a timezone offset. e.g. `-08:00`." diff --git a/src/metabase/util/files.clj b/src/metabase/util/files.clj index 0a79cab2e10..05491ea8460 100644 --- a/src/metabase/util/files.clj +++ b/src/metabase/util/files.clj @@ -9,9 +9,7 @@ [clojure.string :as str] [clojure.tools.logging :as log] [metabase.util :as u] - [metabase.util - [date :as du] - [i18n :refer [trs]]]) + [metabase.util.i18n :refer [trs]]) (:import java.io.FileNotFoundException java.net.URL [java.nio.file CopyOption Files FileSystem FileSystems LinkOption OpenOption Path StandardCopyOption] @@ -76,7 +74,7 @@ (defn- copy-file! [^Path source, ^Path dest] (when (or (not (exists? dest)) (pos? (.compareTo (last-modified-time source) (last-modified-time dest)))) - (du/profile (trs "Extract file {0} -> {1}" source dest) + (u/profile (trs "Extract file {0} -> {1}" source dest) (Files/copy source dest (u/varargs CopyOption [StandardCopyOption/REPLACE_EXISTING]))))) (defn copy-files! diff --git a/src/metabase/util/stats.clj b/src/metabase/util/stats.clj index 2d9e5f27fc5..7ba410f5bbf 100644 --- a/src/metabase/util/stats.clj +++ b/src/metabase/util/stats.clj @@ -3,35 +3,21 @@ (:require [clj-http.client :as client] [clojure.string :as str] [clojure.tools.logging :as log] + [java-time :as t] [medley.core :as m] [metabase [config :as config] [driver :as driver] [email :as email] + [models :refer [Card Collection Dashboard DashboardCard Database Field Metric PermissionsGroup Pulse + PulseCard PulseChannel QueryCache QueryExecution Segment Table User]] [public-settings :as public-settings] [util :as u]] [metabase.api.session :as session-api] [metabase.integrations.slack :as slack] - [metabase.models - [card :refer [Card]] - [collection :refer [Collection]] - [dashboard :refer [Dashboard]] - [dashboard-card :refer [DashboardCard]] - [database :refer [Database]] - [field :refer [Field]] - [humanization :as humanization] - [metric :refer [Metric]] - [permissions-group :refer [PermissionsGroup]] - [pulse :refer [Pulse]] - [pulse-card :refer [PulseCard]] - [pulse-channel :refer [PulseChannel]] - [query-cache :refer [QueryCache]] - [query-execution :refer [QueryExecution]] - [segment :refer [Segment]] - [table :refer [Table]] - [user :refer [User]]] - [toucan.db :as db]) - (:import java.util.Date)) + [metabase.models.humanization :as humanization] + [metabase.util.i18n :refer [trs]] + [toucan.db :as db])) (defn- merge-count-maps "Merge sequence of maps `ms` by summing counts inside them. Non-integer values are allowed; truthy values are @@ -343,7 +329,7 @@ (update-in [:num_by_latency (bin-large-number (/ (:running_time execution) 1000))] u/safe-inc)))) (defn- summarize-executions-per-user - "Convert a map of USER-ID->NUM-EXECUTIONS to the histogram output format we expect." + "Convert a map of `user-id->num-executions` to the histogram output format we expect." [user-id->num-executions] (frequencies (map bin-large-number (vals user-id->num-executions)))) @@ -390,22 +376,23 @@ "generate a map of the usage stats for this instance" [] (merge (instance-settings) - {:uuid (public-settings/site-uuid) :timestamp (Date.)} - {:stats {:cache (cache-metrics) - :collection (collection-metrics) - :dashboard (dashboard-metrics) - :database (database-metrics) - :execution (execution-metrics) - :field (field-metrics) - :group (group-metrics) - :metric (metric-metrics) - :pulse (pulse-metrics) - :alert (alert-metrics) - :question (question-metrics) - :segment (segment-metrics) - :system (system-metrics) - :table (table-metrics) - :user (user-metrics)}})) + {:uuid (public-settings/site-uuid) + :timestamp (t/offset-date-time) + :stats {:cache (cache-metrics) + :collection (collection-metrics) + :dashboard (dashboard-metrics) + :database (database-metrics) + :execution (execution-metrics) + :field (field-metrics) + :group (group-metrics) + :metric (metric-metrics) + :pulse (pulse-metrics) + :alert (alert-metrics) + :question (question-metrics) + :segment (segment-metrics) + :system (system-metrics) + :table (table-metrics) + :user (user-metrics)}})) (defn- send-stats! @@ -414,7 +401,7 @@ (try (client/post metabase-usage-url {:form-params stats, :content-type :json, :throw-entire-message? true}) (catch Throwable e - (log/error "Sending usage stats FAILED:" (.getMessage e))))) + (log/error e (trs "Sending usage stats FAILED"))))) (defn phone-home-stats! diff --git a/test/metabase/api/activity_test.clj b/test/metabase/api/activity_test.clj index 1de5071a569..c55462f71a0 100644 --- a/test/metabase/api/activity_test.clj +++ b/test/metabase/api/activity_test.clj @@ -2,6 +2,9 @@ "Tests for /api/activity endpoints." (:require [clojure.test :refer :all] [expectations :refer [expect]] + [metabase + [db :as mdb] + [util :as u]] [metabase.api.activity :as activity-api] [metabase.models [activity :refer [Activity]] @@ -10,8 +13,6 @@ [view-log :refer [ViewLog]]] [metabase.test.data.users :as test-users] [metabase.test.fixtures :as fixtures] - [metabase.util :as u] - [metabase.util.date :as du] [toucan.db :as db] [toucan.util.test :as tt])) @@ -39,44 +40,45 @@ [:common_name :date_joined :email :first_name :is_qbnewb :is_superuser :last_login :last_name]))) ;; NOTE: timestamp matching was being a real PITA so I cheated a bit. ideally we'd fix that -(tt/expect-with-temp [Activity [activity1 {:topic "install" - :details {} - :timestamp (du/->Timestamp #inst "2015-09-09T12:13:14.888Z")}] - Activity [activity2 {:topic "dashboard-create" - :user_id (test-users/user->id :crowberto) - :model "dashboard" - :model_id 1234 - :details {:description "Because I can!" - :name "Bwahahaha"} - :timestamp (du/->Timestamp #inst "2015-09-10T18:53:01.632Z")}] - Activity [activity3 {:topic "user-joined" - :user_id (test-users/user->id :rasta) - :model "user" - :details {} - :timestamp (du/->Timestamp #inst "2015-09-10T05:33:43.641Z")}]] - (let [activity-ids (fn [activity] - (db/select-one [Activity :id :user_id :details :model :model_id] :id (u/get-id activity)))] - [(merge - activity-defaults - (activity-ids activity2) - {:topic "dashboard-create" - :user (activity-user-info :crowberto)}) - (merge - activity-defaults - (activity-ids activity3) - {:topic "user-joined" - :user (activity-user-info :rasta)}) - (merge - activity-defaults - (activity-ids activity1) - {:topic "install" - :user_id nil - :user nil})]) - ;; remove other activities from the API response just in case -- we're not interested in those - (let [these-activity-ids #{(u/get-id activity1) (u/get-id activity2) (u/get-id activity3)}] - (for [activity ((test-users/user->client :crowberto) :get 200 "activity") - :when (contains? these-activity-ids (u/get-id activity))] - (dissoc activity :timestamp)))) +(deftest activity-list-test + (testing "GET /api/activity" + (tt/with-temp* [Activity [activity1 {:topic "install" + :details {} + :timestamp #t "2015-09-09T12:13:14.888Z[UTC]"}] + Activity [activity2 {:topic "dashboard-create" + :user_id (test-users/user->id :crowberto) + :model "dashboard" + :model_id 1234 + :details {:description "Because I can!" + :name "Bwahahaha"} + :timestamp #t "2015-09-10T18:53:01.632Z[UTC]"}] + Activity [activity3 {:topic "user-joined" + :user_id (test-users/user->id :rasta) + :model "user" + :details {} + :timestamp #t "2015-09-10T05:33:43.641Z[UTC]"}]] + (is (= (letfn [(fetch-activity [activity] + (merge + activity-defaults + (db/select-one [Activity :id :user_id :details :model :model_id] :id (u/get-id activity))))] + [(merge + (fetch-activity activity2) + {:topic "dashboard-create" + :user (activity-user-info :crowberto)}) + (merge + (fetch-activity activity3) + {:topic "user-joined" + :user (activity-user-info :rasta)}) + (merge + (fetch-activity activity1) + {:topic "install" + :user_id nil + :user nil})]) + ;; remove other activities from the API response just in case -- we're not interested in those + (let [these-activity-ids (set (map u/get-id [activity1 activity2 activity3]))] + (for [activity ((test-users/user->client :crowberto) :get 200 "activity") + :when (contains? these-activity-ids (u/get-id activity))] + (dissoc activity :timestamp)))))))) ;;; GET /recent_views @@ -92,59 +94,61 @@ :user_id user :model model :model_id model-id - :timestamp (du/new-sql-timestamp)) + :timestamp :%now) ;; we sleep a bit to ensure no events have the same timestamp ;; sadly, MySQL doesn't support milliseconds so we have to wait a second ;; otherwise our records are out of order and this test fails :( - (Thread/sleep 1000)) - -(tt/expect-with-temp [Card [card1 {:name "rand-name" - :creator_id (test-users/user->id :crowberto) - :display "table" - :dataset_query {} - :visualization_settings {}}] - Dashboard [dash1 {:name "rand-name" - :description "rand-name" - :creator_id (test-users/user->id :crowberto)}] - Card [card2 {:name "rand-name" - :creator_id (test-users/user->id :crowberto) - :display "table" - :dataset_query {} - :visualization_settings {}}]] - [{:cnt 1 - :user_id (test-users/user->id :crowberto) - :model "card" - :model_id (:id card1) - :model_object {:id (:id card1) - :name (:name card1) - :collection_id nil - :description (:description card1) - :display (name (:display card1))}} - {:cnt 1 - :user_id (test-users/user->id :crowberto) - :model "dashboard" - :model_id (:id dash1) - :model_object {:id (:id dash1) - :name (:name dash1) - :collection_id nil - :description (:description dash1)}} - {:cnt 1 - :user_id (test-users/user->id :crowberto) - :model "card" - :model_id (:id card2) - :model_object {:id (:id card2) - :name (:name card2) - :collection_id nil - :description (:description card2) - :display (name (:display card2))}}] - (do + (Thread/sleep (if (= (mdb/db-type) :mysql) + 1000 + 10))) + +(deftest recent-views-test + (tt/with-temp* [Card [card1 {:name "rand-name" + :creator_id (test-users/user->id :crowberto) + :display "table" + :dataset_query {} + :visualization_settings {}}] + Dashboard [dash1 {:name "rand-name" + :description "rand-name" + :creator_id (test-users/user->id :crowberto)}] + Card [card2 {:name "rand-name" + :creator_id (test-users/user->id :crowberto) + :display "table" + :dataset_query {} + :visualization_settings {}}]] (create-view! (test-users/user->id :crowberto) "card" (:id card2)) (create-view! (test-users/user->id :crowberto) "dashboard" (:id dash1)) (create-view! (test-users/user->id :crowberto) "card" (:id card1)) (create-view! (test-users/user->id :crowberto) "card" 36478) (create-view! (test-users/user->id :rasta) "card" (:id card1)) - (for [recent-view ((test-users/user->client :crowberto) :get 200 "activity/recent_views")] - (dissoc recent-view :max_ts)))) + (is (= [{:cnt 1 + :user_id (test-users/user->id :crowberto) + :model "card" + :model_id (:id card1) + :model_object {:id (:id card1) + :name (:name card1) + :collection_id nil + :description (:description card1) + :display (name (:display card1))}} + {:cnt 1 + :user_id (test-users/user->id :crowberto) + :model "dashboard" + :model_id (:id dash1) + :model_object {:id (:id dash1) + :name (:name dash1) + :collection_id nil + :description (:description dash1)}} + {:cnt 1 + :user_id (test-users/user->id :crowberto) + :model "card" + :model_id (:id card2) + :model_object {:id (:id card2) + :name (:name card2) + :collection_id nil + :description (:description card2) + :display (name (:display card2))}}] + (for [recent-view ((test-users/user->client :crowberto) :get 200 "activity/recent_views")] + (dissoc recent-view :max_ts)))))) ;;; activities->referenced-objects, referenced-objects->existing-objects, add-model-exists-info @@ -188,16 +192,21 @@ {:model "dashboard", :model_id 0, :topic :dashboard-remove-cards, :details {:dashcards [{:card_id card-id} {:card_id 0}]}}])) -;; Only admins should get to see user-joined activities (defn- user-can-see-user-joined-activity? [user] ;; clear out all existing Activity entries (db/delete! Activity) (-> (tt/with-temp Activity [activity {:topic "user-joined" :details {} - :timestamp (du/->Timestamp #inst "2019-02-15T11:55:00.000Z")}] + :timestamp #t "2019-02-15T11:55:00.000Z"}] ((test-users/user->client user) :get 200 "activity")) seq boolean)) -(expect true (user-can-see-user-joined-activity? :crowberto)) -(expect false (user-can-see-user-joined-activity? :rasta)) +(deftest activity-visibility-test + (testing "Only admins should get to see user-joined activities" + (is (= true + (user-can-see-user-joined-activity? :crowberto)) + "admin should see `:user-joined` activities") + (is (= false + (user-can-see-user-joined-activity? :rasta)) + "non-admin should *not* see `:user-joined` activities"))) diff --git a/test/metabase/api/alert_test.clj b/test/metabase/api/alert_test.clj index 7fd652707c7..aa43a65e48a 100644 --- a/test/metabase/api/alert_test.clj +++ b/test/metabase/api/alert_test.clj @@ -53,6 +53,7 @@ (defn- default-email-channel ([pulse-card] (default-email-channel pulse-card [(fetch-user :rasta)])) + ([pulse-card recipients] {:id pulse-card :enabled true diff --git a/test/metabase/api/card_test.clj b/test/metabase/api/card_test.clj index 791a945d20f..7bc490d90c2 100644 --- a/test/metabase/api/card_test.clj +++ b/test/metabase/api/card_test.clj @@ -33,7 +33,6 @@ [data :as data] [util :as tu]] [metabase.test.data.users :as test-users] - [metabase.util.date :as du] [toucan.db :as db] [toucan.util.test :as tt]) (:import java.io.ByteArrayInputStream @@ -207,15 +206,15 @@ ;; 3 was viewed most recently, followed by 4, then 1. Card 2 was viewed by a different user so ;; shouldn't be returned ViewLog [_ {:model "card", :model_id (u/get-id card-1), :user_id (test-users/user->id :rasta) - :timestamp (du/->Timestamp #inst "2015-12-01")}] + :timestamp #t "2015-12-01"}] ViewLog [_ {:model "card", :model_id (u/get-id card-2), :user_id (test-users/user->id :trashbird) - :timestamp (du/->Timestamp #inst "2016-01-01")}] + :timestamp #t "2016-01-01"}] ViewLog [_ {:model "card", :model_id (u/get-id card-3), :user_id (test-users/user->id :rasta) - :timestamp (du/->Timestamp #inst "2016-02-01")}] + :timestamp #t "2016-02-01"}] ViewLog [_ {:model "card", :model_id (u/get-id card-4), :user_id (test-users/user->id :rasta) - :timestamp (du/->Timestamp #inst "2016-03-01")}] + :timestamp #t "2016-03-01"}] ViewLog [_ {:model "card", :model_id (u/get-id card-3), :user_id (test-users/user->id :rasta) - :timestamp (du/->Timestamp #inst "2016-04-01")}]] + :timestamp #t "2016-04-01"}]] (with-cards-in-readable-collection [card-1 card-2 card-3 card-4] (map :name ((test-users/user->client :rasta) :get 200 "card", :f :recent))))) diff --git a/test/metabase/api/database_test.clj b/test/metabase/api/database_test.clj index 9fb3cfb6b0a..dcfad8686d7 100644 --- a/test/metabase/api/database_test.clj +++ b/test/metabase/api/database_test.clj @@ -117,11 +117,11 @@ (tu/expect-schema (merge (m/map-vals s/eq default-db-details) - {:created_at java.sql.Timestamp + {:created_at java.time.temporal.Temporal :engine (s/eq ::test-driver) :id su/IntGreaterThanZero :details (s/eq {:db "my_db"}) - :updated_at java.sql.Timestamp + :updated_at java.time.temporal.Temporal :name su/NonBlankString :features (s/eq (driver.u/features ::test-driver))}) (create-db-via-api!)) diff --git a/test/metabase/api/dataset_test.clj b/test/metabase/api/dataset_test.clj index db2f9e980cb..c4981b3a3de 100644 --- a/test/metabase/api/dataset_test.clj +++ b/test/metabase/api/dataset_test.clj @@ -32,6 +32,9 @@ (:import com.fasterxml.jackson.core.JsonGenerator)) (defn- format-response [m] + (when-not (map? m) + (throw (ex-info (format "Expected results to be a map! Got: %s" (u/pprint-to-str m)) + {:results m}))) (into {} (for [[k v] (-> m diff --git a/test/metabase/api/table_test.clj b/test/metabase/api/table_test.clj index 8b31d89ad72..6ebd84328f1 100644 --- a/test/metabase/api/table_test.clj +++ b/test/metabase/api/table_test.clj @@ -495,8 +495,8 @@ :dimension_options (var-get #'table-api/datetime-dimension-indexes) :fingerprint {:global {:distinct-count 15 :nil% 0.0}, - :type {:type/DateTime {:earliest "2014-01-01T08:30:00.000Z", - :latest "2014-12-05T15:15:00.000Z"}}}}]}) + :type {:type/DateTime {:earliest "2014-01-01T08:30:00" + :latest "2014-12-05T15:15:00"}}}}]}) (do ;; run the Card which will populate its result_metadata column ((test-users/user->client :crowberto) :post 200 (format "card/%d/query" (u/get-id card))) diff --git a/test/metabase/api/task_test.clj b/test/metabase/api/task_test.clj index 2b9aed04cce..510bc830394 100644 --- a/test/metabase/api/task_test.clj +++ b/test/metabase/api/task_test.clj @@ -1,11 +1,10 @@ (ns metabase.api.task-test - (:require [clj-time.core :as time] - [expectations :refer :all] + (:require [expectations :refer :all] + [java-time :as t] [metabase.models.task-history :refer [TaskHistory]] [metabase.test.data.users :as users] [metabase.test.util :as tu] [metabase.util :as u] - [metabase.util.date :as du] [toucan.db :as db] [toucan.util.test :as tt])) @@ -16,12 +15,12 @@ "Creates `n` task history maps with guaranteed increasing `:ended_at` times. This means that when stored and queried via the GET `/` endpoint, will return in reverse order from how this function returns the task history maps." [n] - (let [task-names (repeatedly n tu/random-name) - now (time/now)] + (let [now (t/zoned-date-time) + task-names (repeatedly n tu/random-name)] (map-indexed (fn [idx task-name] {:task task-name - :started_at (du/->Timestamp now) - :ended_at (du/->Timestamp (time/plus now (time/seconds idx)))}) + :started_at now + :ended_at (t/plus now (t/seconds idx))}) task-names))) ;; Only superusers can query for TaskHistory diff --git a/test/metabase/api/transform_test.clj b/test/metabase/api/transform_test.clj index 2b59d806d1a..96864f9a993 100644 --- a/test/metabase/api/transform_test.clj +++ b/test/metabase/api/transform_test.clj @@ -1,5 +1,6 @@ (ns metabase.api.transform-test - (:require [expectations :refer :all] + (:require [clojure.test :refer :all] + [expectations :refer :all] [metabase.models [card :refer [Card]] [collection :refer [Collection]] @@ -9,10 +10,13 @@ [metabase.test [data :as data] [domain-entities :refer :all] + [fixtures :as fixtures] [transforms :refer :all] [util :as tu]] [metabase.test.data.users :as test-users])) +(use-fixtures :once (fixtures/initialize :db)) + (defn- test-endpoint [] (format "transform/%s/%s/%s" (data/id) "PUBLIC" "Test transform")) diff --git a/test/metabase/automagic_dashboards/core_test.clj b/test/metabase/automagic_dashboards/core_test.clj index 6d7da53a9e1..fc78a207d1e 100644 --- a/test/metabase/automagic_dashboards/core_test.clj +++ b/test/metabase/automagic_dashboards/core_test.clj @@ -1,10 +1,8 @@ (ns metabase.automagic-dashboards.core-test - (:require [clj-time - [core :as t] - [format :as t.format]] - [clojure.core.async :as a] + (:require [clojure.core.async :as a] [clojure.test :refer :all] [expectations :refer :all] + [java-time :as t] [metabase.automagic-dashboards [core :as magic :refer :all] [rules :as rules]] @@ -24,7 +22,7 @@ [data :as data] [util :as tu]] [metabase.util - [date :as date] + [date-2 :as u.date] [i18n :refer [tru]]] [toucan.db :as db] [toucan.util.test :as tt])) @@ -442,79 +440,51 @@ ;;; ------------------- Datetime resolution inference ------------------- -(expect - :month - (#'magic/optimal-datetime-resolution - {:fingerprint {:type {:type/DateTime {:earliest "2015" - :latest "2017"}}}})) - -(expect - :day - (#'magic/optimal-datetime-resolution - {:fingerprint {:type {:type/DateTime {:earliest "2017-01-01" - :latest "2017-03-04"}}}})) - -(expect - :year - (#'magic/optimal-datetime-resolution - {:fingerprint {:type {:type/DateTime {:earliest "2005" - :latest "2017"}}}})) - -(expect - :hour - (#'magic/optimal-datetime-resolution - {:fingerprint {:type {:type/DateTime {:earliest "2017-01-01" - :latest "2017-01-02"}}}})) - -(expect - :minute - (#'magic/optimal-datetime-resolution - {:fingerprint {:type {:type/DateTime {:earliest "2017-01-01T00:00:00" - :latest "2017-01-01T00:02:00"}}}})) +(deftest optimal-datetime-resolution-test + (doseq [[m expected] [[{:earliest "2015" + :latest "2017"} + :month] + [{:earliest "2017-01-01" + :latest "2017-03-04"} + :day] + [{:earliest "2005" + :latest "2017"} + :year] + [{:earliest "2017-01-01" + :latest "2017-01-02"} + :hour] + [{:earliest "2017-01-01T00:00:00" + :latest "2017-01-01T00:02:00"} + :minute]] + :let [fingerprint {:type {:type/DateTime m}}]] + (testing (format "fingerprint = %s" (pr-str fingerprint)) + (is (= expected + (#'magic/optimal-datetime-resolution {:fingerprint fingerprint})))))) ;;; ------------------- Datetime humanization (for chart and dashboard titles) ------------------- -(let [tz (-> date/jvm-timezone deref ^TimeZone .getID) - dt (t/from-time-zone (t/date-time 1990 9 9 12 30) - (t/time-zone-for-id tz)) - unparse-with-formatter (fn [formatter dt] - (t.format/unparse - (t.format/formatter formatter (t/time-zone-for-id tz)) dt))] - (expect - (map str [(tru "at {0}" (unparse-with-formatter "h:mm a, MMMM d, YYYY" dt)) - (tru "at {0}" (unparse-with-formatter "h a, MMMM d, YYYY" dt)) - (tru "on {0}" (unparse-with-formatter "MMMM d, YYYY" dt)) - (tru "in {0} week - {1}" - (#'magic/pluralize (date/date-extract :week-of-year dt tz)) - (str (date/date-extract :year dt tz))) - (tru "in {0}" (unparse-with-formatter "MMMM YYYY" dt)) - (tru "in Q{0} - {1}" - (date/date-extract :quarter-of-year dt tz) - (str (date/date-extract :year dt tz))) - (unparse-with-formatter "YYYY" dt) - (unparse-with-formatter "EEEE" dt) - (tru "at {0}" (unparse-with-formatter "h a" dt)) - (unparse-with-formatter "MMMM" dt) - (tru "Q{0}" (date/date-extract :quarter-of-year dt tz)) - (date/date-extract :minute-of-hour dt tz) - (date/date-extract :day-of-month dt tz) - (date/date-extract :week-of-year dt tz)]) - (let [dt (t.format/unparse (t.format/formatters :date-hour-minute-second) dt)] - (map (comp str (partial #'magic/humanize-datetime dt)) [:minute - :hour - :day - :week - :month - :quarter - :year - :day-of-week - :hour-of-day - :month-of-year - :quarter-of-year - :minute-of-hour - :day-of-month - :week-of-year])))) +(deftest temporal-humanization-test + (let [tz "UTC" + dt #t "1990-09-09T12:30" + t-str "1990-09-09T12:30:00"] + (doseq [[unit expected] {:minute (tru "at {0}" (t/format "h:mm a, MMMM d, YYYY" dt)) + :hour (tru "at {0}" (t/format "h a, MMMM d, YYYY" dt)) + :day (tru "on {0}" (t/format "MMMM d, YYYY" dt)) + :week (tru "in {0} week - {1}" (#'magic/pluralize (u.date/extract dt :week-of-year)) (str (u.date/extract dt :year))) + :month (tru "in {0}" (t/format "MMMM YYYY" dt)) + :quarter (tru "in Q{0} - {1}" (u.date/extract dt :quarter-of-year) (str (u.date/extract dt :year))) + :year (t/format "YYYY" dt) + :day-of-week (t/format "EEEE" dt) + :hour-of-day (tru "at {0}" (t/format "h a" dt)) + :month-of-year (t/format "MMMM" dt) + :quarter-of-year (tru "Q{0}" (u.date/extract dt :quarter-of-year)) + :minute-of-hour (u.date/extract dt :minute-of-hour) + :day-of-month (u.date/extract dt :day-of-month) + :week-of-year (u.date/extract dt :week-of-year)}] + (testing (format "unit = %s" unit) + (is (= (str expected) + (str (#'magic/humanize-datetime t-str unit)))))))) (deftest pluralize-test (are [expected n] (= (str expected) @@ -527,6 +497,7 @@ (deftest handlers-test (testing "Make sure we have handlers for all the units available" - (doseq [unit (concat date/date-extract-units date/date-trunc-units)] + (doseq [unit (disj (set (concat u.date/extract-units u.date/truncate-units)) + :iso-day-of-week :iso-day-of-year :iso-week :iso-week-of-year :millisecond)] (testing unit (is (some? (#'magic/humanize-datetime "1990-09-09T12:30:00" unit))))))) diff --git a/test/metabase/cmd/compare_h2_dbs.clj b/test/metabase/cmd/compare_h2_dbs.clj index f2fe0acb00c..1d495d1dc7e 100644 --- a/test/metabase/cmd/compare_h2_dbs.clj +++ b/test/metabase/cmd/compare_h2_dbs.clj @@ -5,8 +5,9 @@ [pprint :as pprint] [string :as str]] [clojure.java.jdbc :as jdbc] - [metabase.util :as u]) - (:import org.h2.jdbc.JdbcClob)) + metabase.db.jdbc-protocols)) + +(comment metabase.db.jdbc-protocols/keep-me) (defn- jdbc-spec [db-file] {:classname "org.h2.Driver" @@ -42,7 +43,7 @@ (jdbc/with-db-metadata [metadata spec] (let [result (jdbc/metadata-result (.getTables metadata nil nil nil - (into-array String ["TABLE", "VIEW", "FOREIGN TABLE", "MATERIALIZED VIEW"])))] + (into-array String ["TABLE" "VIEW" "FOREIGN TABLE" "MATERIALIZED VIEW"])))] (sort (remove ignored-table-names (map :table_name result)))))) (defmulti ^:private normalize-value @@ -52,10 +53,6 @@ [v] v) -(defmethod normalize-value JdbcClob - [v] - (u/jdbc-clob->str v)) - (def ^:private ignored-keys #{:created_at :updated_at :timestamp :last_login :date_joined :last_analyzed}) @@ -77,8 +74,7 @@ (->> rows (mapv normalize-values) sort-rows))) (defn- different-table-names? - "Diff the table names in two DBs. Returns a truthy value if there is a difference. - the same." + "True if the set of tables names is different between DBs represented by `conn-1` and `conn-2`." [conn-1 conn-2] (let [[table-names-1 table-names-2] (map table-names [conn-1 conn-2]) _ (printf "Diffing %d/%d table names...\n" (count table-names-1) (count table-names-2)) diff --git a/test/metabase/cmd/load_from_h2_test.clj b/test/metabase/cmd/load_from_h2_test.clj index 7c1b2690095..e2f44f61fb6 100644 --- a/test/metabase/cmd/load_from_h2_test.clj +++ b/test/metabase/cmd/load_from_h2_test.clj @@ -1,11 +1,9 @@ (ns metabase.cmd.load-from-h2-test (:require [expectations :refer [expect]] [flatland.ordered.map :as ordered-map] - [metabase - [db :as mdb] - [util :as u]] [metabase.cmd.load-from-h2 :as load-from-h2] [metabase.plugins.classloader :as classloader] + [metabase.util :as u] [toucan [db :as db] [models :as models]])) @@ -68,26 +66,3 @@ :sizex 18 :sizey 9)]) (update :cols vec)))) - -;; make sure `objects->colums+values` properly de-CLOBs and CLOBs (by calling `u/jdbc-clob->str`) - -(defrecord ^:private FakeClob [s]) - -(expect - {:cols ["\"created_at\"" "\"description\"" "\"parameter_mappings\"" "\"visualization_settings\""] - :vals [[#inst "2019-04-05T21:26:39.936-00:00" - "This is a description" - [] - {}]]} - (binding [db/*quoting-style* :ansi] - (with-redefs [mdb/db-type (constantly :postgres) - u/jdbc-clob->str #(cond-> % - (instance? FakeClob %) :s)] - (-> (#'load-from-h2/objects->colums+values - [(ordered-map/ordered-map - :created_at #inst "2019-04-05T21:26:39.936000000-00:00" - :description (FakeClob. "This is a description") - :parameter_mappings [] - :visualization_settings {})]) - (update :cols vec) - (update :vals vec))))) diff --git a/test/metabase/db/migrations_test.clj b/test/metabase/db/migrations_test.clj index 53ac7e17ba6..fb280c2d048 100644 --- a/test/metabase/db/migrations_test.clj +++ b/test/metabase/db/migrations_test.clj @@ -1,7 +1,9 @@ (ns metabase.db.migrations-test "Tests to make sure the data migrations actually work as expected and don't break things. Shamefully, we have way less of these than we should... but that doesn't mean we can't write them for our new ones :)" - (:require [clojure.set :as set] + (:require [clojure + [set :as set] + [test :refer :all]] [expectations :refer :all] [medley.core :as m] [metabase.db.migrations :as migrations] @@ -15,12 +17,15 @@ [pulse :refer [Pulse]] [user :refer [User]]] [metabase.test.data.datasets :as datasets] + [metabase.test.fixtures :as fixtures] [metabase.test.util.log :as tu.log] [metabase.util :as u] [metabase.util.password :as upass] [toucan.db :as db] [toucan.util.test :as tt])) +(use-fixtures :once (fixtures/initialize :db)) + ;; add-legacy-sql-directive-to-bigquery-sql-cards ;; ;; only run this test when we're running tests for BigQuery because when a Database gets deleted it calls diff --git a/test/metabase/driver/mysql_test.clj b/test/metabase/driver/mysql_test.clj index 8c9ca89bcb5..4134c588fde 100644 --- a/test/metabase/driver/mysql_test.clj +++ b/test/metabase/driver/mysql_test.clj @@ -1,6 +1,5 @@ (ns metabase.driver.mysql-test - (:require [clj-time.core :as t] - [clojure + (:require [clojure [string :as str] [test :refer :all]] [clojure.java.jdbc :as jdbc] @@ -22,7 +21,6 @@ [datasets :as datasets :refer [expect-with-driver]] [interface :as tx]] [metabase.test.util.timezone :as tu.tz] - [metabase.util.date :as du] [toucan.db :as db] [toucan.util.test :as tt])) @@ -117,8 +115,8 @@ "Should add a `+` if needed to offset")))) -(def ^:private before-daylight-savings (du/str->date-time "2018-03-10 10:00:00" du/utc)) -(def ^:private after-daylight-savings (du/str->date-time "2018-03-12 10:00:00" du/utc)) +(def ^:private before-daylight-savings #t "2018-03-10T10:00:00Z") +(def ^:private after-daylight-savings #t "2018-03-12T10:00:00Z") ;; Most of our tests either deal in UTC (offset 00:00) or America/Los_Angeles timezones (-07:00/-08:00). When dealing ;; with dates, we will often truncate the timestamp to a date. When we only test with negative timezone offsets, in @@ -131,10 +129,10 @@ ;; This test ensures if our JVM timezone and reporting timezone are Asia/Hong_Kong, we get a correctly formatted date (expect-with-driver :mysql ["2018-04-18T00:00:00+08:00"] - (tu.tz/with-jvm-tz (t/time-zone-for-id "Asia/Hong_Kong") + (tu.tz/with-system-timezone-id "Asia/Hong_Kong" (tu/with-temporary-setting-values [report-timezone "Asia/Hong_Kong"] (qp.test/first-row - (du/with-effective-timezone (data/db) + (identity #_du/with-effective-timezone #_(data/db) (qp/process-query {:database (data/id) :type :native @@ -154,10 +152,10 @@ ;; off by a day (expect-with-driver :mysql ["2018-04-18T00:00:00-07:00"] - (tu.tz/with-jvm-tz (t/time-zone-for-id "Asia/Hong_Kong") + (tu.tz/with-system-timezone-id "Asia/Hong_Kong" (tu/with-temporary-setting-values [report-timezone "America/Los_Angeles"] (qp.test/first-row - (du/with-effective-timezone (data/db) + (identity #_du/with-effective-timezone #_(data/db) (qp/process-query {:database (data/id) :type :native diff --git a/test/metabase/driver/postgres_test.clj b/test/metabase/driver/postgres_test.clj index 6aff8556acd..76045a0e941 100644 --- a/test/metabase/driver/postgres_test.clj +++ b/test/metabase/driver/postgres_test.clj @@ -325,12 +325,12 @@ (sync/sync-database! database) (is (= {"start_time" {:global {:distinct-count 1 :nil% 0.0} - :type {:type/DateTime {:earliest "1970-01-01T22:00:00.000Z" - :latest "1970-01-01T22:00:00.000Z"}}} + :type {:type/DateTime {:earliest "22:00:00" + :latest "22:00:00"}}} "end_time" {:global {:distinct-count 1 :nil% 0.0} - :type {:type/DateTime {:earliest "1970-01-01T09:00:00.000Z" - :latest "1970-01-01T09:00:00.000Z"}}} + :type {:type/DateTime {:earliest "09:00:00" + :latest "09:00:00"}}} "reason" {:global {:distinct-count 1 :nil% 0.0} :type {:type/Text {:percent-json 0.0 diff --git a/test/metabase/driver/sql/query_processor_test.clj b/test/metabase/driver/sql/query_processor_test.clj index b0ed17c7c75..590fb5eaf8e 100644 --- a/test/metabase/driver/sql/query_processor_test.clj +++ b/test/metabase/driver/sql/query_processor_test.clj @@ -102,7 +102,7 @@ :from [(id :table "PUBLIC" "CHECKINS")] :where [:> (id :field "PUBLIC" "CHECKINS" "DATE") - #inst "2015-01-01T00:00:00.000-00:00"]} + #t "2015-01-01T00:00:00.000-00:00"]} (id :table-alias "source")]] :left-join [[(id :table "PUBLIC" "VENUES") (bound-alias :source (id :table-alias "v"))] [:= @@ -123,7 +123,7 @@ :fields [$id [:datetime-field $date :default] $user_id $venue_id] :filter [:> $date - [:absolute-datetime #inst "2015-01-01T00:00:00.000000000-00:00" :default]],}, + [:absolute-datetime #t "2015-01-01T00:00:00.000000000-00:00" :default]],}, :aggregation [[:count]] :order-by [[:asc [:joined-field "v" $venues.name]]] :breakout [[:joined-field "v" $venues.name]], diff --git a/test/metabase/driver/sql_jdbc_test.clj b/test/metabase/driver/sql_jdbc_test.clj index 2dddbdb89ae..bba9d464286 100644 --- a/test/metabase/driver/sql_jdbc_test.clj +++ b/test/metabase/driver/sql_jdbc_test.clj @@ -3,7 +3,8 @@ [metabase [driver :as driver] [query-processor :as qp] - [query-processor-test :as qp.test]] + [query-processor-test :as qp.test] + [util :as u]] [metabase.db.metadata-queries :as metadata-queries] [metabase.driver.util :as driver.u] [metabase.models @@ -14,12 +15,11 @@ [datasets :as datasets] [env :as tx.env] [interface :as tx]] - [metabase.test.util.log :as tu.log] - [metabase.util.date :as du])) + [metabase.test.util.log :as tu.log])) (defonce ^:private sql-jdbc-drivers* (delay - (du/profile "resolve sql-jdbc-drivers" + (u/profile "resolve sql-jdbc-drivers" (set (for [driver (tx.env/test-drivers) :when (isa? driver/hierarchy (driver/the-driver driver) (driver/the-driver :sql-jdbc))] @@ -77,34 +77,34 @@ :dest-column-name "ID"}} (driver/describe-table-fks :h2 (data/db) (Table (data/id :venues)))))) -;;; TABLE-ROWS-SAMPLE -(datasets/expect-with-drivers (sql-jdbc-drivers) - [["20th Century Cafe"] - ["25°"] - ["33 Taps"] - ["800 Degrees Neapolitan Pizzeria"] - ["BCD Tofu House"]] - (->> (metadata-queries/table-rows-sample (Table (data/id :venues)) - [(Field (data/id :venues :name))]) - ;; since order is not guaranteed do some sorting here so we always get the same results - (sort-by first) - (take 5))) - -;;; TABLE-ROWS-SEQ -(datasets/expect-with-drivers (sql-jdbc-drivers) - [{:name "Red Medicine", :price 3, :category_id 4, :id 1} - {:name "Stout Burgers & Beers", :price 2, :category_id 11, :id 2} - {:name "The Apple Pan", :price 2, :category_id 11, :id 3} - {:name "Wurstküche", :price 2, :category_id 29, :id 4} - {:name "Brite Spot Family Restaurant", :price 2, :category_id 20, :id 5}] - (for [row (take 5 (sort-by :id (driver/table-rows-seq driver/*driver* - (data/db) - (Table (data/id :venues)))))] - ;; different DBs use different precisions for these - (-> (dissoc row :latitude :longitude) - (update :price int) - (update :category_id int) - (update :id int)))) +(deftest table-rows-sample-test + (datasets/test-drivers (sql-jdbc-drivers) + (is (= [["20th Century Cafe"] + ["25°"] + ["33 Taps"] + ["800 Degrees Neapolitan Pizzeria"] + ["BCD Tofu House"]] + (->> (metadata-queries/table-rows-sample (Table (data/id :venues)) + [(Field (data/id :venues :name))]) + ;; since order is not guaranteed do some sorting here so we always get the same results + (sort-by first) + (take 5)))))) + +(deftest table-rows-seq-test + (datasets/test-drivers (sql-jdbc-drivers) + (is (= [{:name "Red Medicine", :price 3, :category_id 4, :id 1} + {:name "Stout Burgers & Beers", :price 2, :category_id 11, :id 2} + {:name "The Apple Pan", :price 2, :category_id 11, :id 3} + {:name "Wurstküche", :price 2, :category_id 29, :id 4} + {:name "Brite Spot Family Restaurant", :price 2, :category_id 20, :id 5}] + (for [row (take 5 (sort-by :id (driver/table-rows-seq driver/*driver* + (data/db) + (Table (data/id :venues)))))] + ;; different DBs use different precisions for these + (-> (dissoc row :latitude :longitude) + (update :price int) + (update :category_id int) + (update :id int))))))) ;;; Make sure invalid ssh credentials are detected if a direct connection is possible (datasets/expect-with-driver :postgres diff --git a/test/metabase/http_client.clj b/test/metabase/http_client.clj index 23a1ae7f2f1..05f4fd4fe07 100644 --- a/test/metabase/http_client.clj +++ b/test/metabase/http_client.clj @@ -6,12 +6,13 @@ [string :as str] [test :as t]] [clojure.tools.logging :as log] + [java-time :as java-time] [metabase [config :as config] [util :as u]] [metabase.middleware.session :as mw.session] [metabase.test.initialize :as initialize] - [metabase.util.date :as du] + [metabase.util.date-2 :as u.date] [schema.core :as s])) ;;; build-url @@ -23,7 +24,7 @@ (defn build-url "Build an API URL for `localhost` and `MB_JETTY_PORT` with `url-param-kwargs`. - (build-url \"db/1\" {:x true}) -> \"http://localhost:3000/api/db/1?x=true\"" + (build-url \"db/1\" {:x true}) -> \"http://localhost:3000/api/db/1?x=true\"" [url url-param-kwargs] {:pre [(string? url) (u/maybe? map? url-param-kwargs)]} (str *url-prefix* url (when (seq url-param-kwargs) @@ -35,23 +36,41 @@ ;;; parse-response -(def ^:private ^:const auto-deserialize-dates-keys +(def ^:private auto-deserialize-dates-keys #{:created_at :updated_at :last_login :date_joined :started_at :finished_at :last_analyzed}) (defn- auto-deserialize-dates "Automatically recurse over `response` and look for keys that are known to correspond to dates. Parse their values and - convert to `java.sql.Timestamps`." + convert to java temporal types." [response] - (cond (sequential? response) (map auto-deserialize-dates response) - (map? response) (->> response - (map (fn [[k v]] - {k (cond - ;; Our tests only run in UTC, parsing timestamp strings as UTC - (contains? auto-deserialize-dates-keys k) (du/->Timestamp v du/utc) - (coll? v) (auto-deserialize-dates v) - :else v)})) - (into {})) - :else response)) + (cond (sequential? response) + (map auto-deserialize-dates response) + + (map? response) + (->> response + (map (fn [[k v]] + {k (cond + ;; `u.date/parse` converts OffsetDateTimes with `Z` offset to + ;; `ZonedDateTime` automatically (for better or worse) since this + ;; won't match what's actually in the DB convert it back to an `OffsetDateTime` + (contains? auto-deserialize-dates-keys k) + (try + (let [parsed (u.date/parse v)] + (if (java-time/zoned-date-time? parsed) + (java-time/offset-date-time parsed) + parsed)) + (catch Throwable _ + v)) + + (coll? v) + (auto-deserialize-dates v) + + :else + v)})) + (into {})) + + :else + response)) (defn- parse-response "Deserialize the JSON response or return as-is if that fails." @@ -60,7 +79,7 @@ body (try (auto-deserialize-dates (json/parse-string body keyword)) - (catch Throwable _ + (catch Throwable e (when-not (str/blank? body) body))))) @@ -69,7 +88,7 @@ (declare client) -(s/defn authenticate +(s/defn authenticate :- s/Str "Authenticate a test user with `username` and `password`, returning their Metabase Session token; or throw an Exception if that fails." [credentials :- {:username s/Str, :password s/Str}] @@ -150,11 +169,14 @@ [body [& {:as url-param-kwargs}]] (u/optional map? args)] [credentials method expected-status url body url-param-kwargs request-options])) +(def ^:private response-timeout-ms (* 15 1000)) + (defn client-full-response "Identical to `client` except returns the full HTTP response map, not just the body of the response" {:arglists '([credentials? method expected-status-code? url request-options? http-body-map? & url-kwargs])} [& args] - (apply -client (parse-http-client-args args))) + (u/with-timeout response-timeout-ms + (apply -client (parse-http-client-args args)))) (defn client "Perform an API call and return the response (for test purposes). diff --git a/test/metabase/metabot/instance_test.clj b/test/metabase/metabot/instance_test.clj index a5df6b828c8..943d97c6e62 100644 --- a/test/metabase/metabot/instance_test.clj +++ b/test/metabase/metabot/instance_test.clj @@ -1,7 +1,7 @@ (ns metabase.metabot.instance-test (:require [expectations :refer [expect]] [metabase.metabot.instance :as metabot.instance] - [metabase.util.date :as du])) + [metabase.util.date-2 :as u.date])) ;; test that if we're not the MetaBot based on Settings, our function to check is working correctly (expect @@ -23,7 +23,7 @@ (expect (do (#'metabot.instance/metabot-instance-uuid (str (java.util.UUID/randomUUID))) - (#'metabot.instance/metabot-instance-last-checkin (du/relative-date :minute -10 (#'metabot.instance/current-timestamp-from-db))) + (#'metabot.instance/metabot-instance-last-checkin (u.date/add (#'metabot.instance/current-timestamp-from-db) :minute -10)) (#'metabot.instance/check-and-update-instance-status!) (#'metabot.instance/am-i-the-metabot?))) diff --git a/test/metabase/middleware/auth_test.clj b/test/metabase/middleware/auth_test.clj index 5aca8276d6a..05752ab58bc 100644 --- a/test/metabase/middleware/auth_test.clj +++ b/test/metabase/middleware/auth_test.clj @@ -1,5 +1,6 @@ (ns metabase.middleware.auth-test (:require [expectations :refer [expect]] + [java-time :as t] [metabase.middleware [auth :as mw.auth] [session :as mw.session] @@ -9,8 +10,7 @@ [ring.mock.request :as mock] [toucan.db :as db] [toucan.util.test :as tt]) - (:import java.sql.Timestamp - java.util.UUID)) + (:import java.util.UUID)) ;; create a simple example of our middleware wrapped around a handler that simply returns the request (defn- auth-enforced-handler [request] @@ -60,7 +60,7 @@ (tt/with-temp Session [_ {:id session-id :user_id (test-users/user->id :rasta)}] (db/update-where! Session {:id session-id} - :created_at (Timestamp. 0)) + :created_at (t/instant 0)) (auth-enforced-handler (request-with-session-id session-id))))) diff --git a/test/metabase/middleware/session_test.clj b/test/metabase/middleware/session_test.clj index 11423c54cd9..e5ae7017563 100644 --- a/test/metabase/middleware/session_test.clj +++ b/test/metabase/middleware/session_test.clj @@ -1,68 +1,73 @@ (ns metabase.middleware.session-test - (:require [environ.core :as env] + (:require [clojure.test :refer :all] + [environ.core :as env] [expectations :refer [expect]] + [java-time :as t] [metabase.api.common :refer [*current-user* *current-user-id*]] [metabase.middleware.session :as mw.session] [metabase.test.data.users :as test-users] [ring.mock.request :as mock]) - (:import java.util.UUID - org.joda.time.DateTime)) - -;;; ----------------------------------------------- set-session-cookie ----------------------------------------------- - -;; let's see whether we can set a Session cookie using the default options -(let [uuid (UUID/randomUUID)] - (expect - ;; should unset the old SESSION_ID if it's present - {"metabase.SESSION_ID" - {:value nil - :expires (DateTime. 0) - :path "/"} - "metabase.SESSION" - {:value (str uuid) - :same-site :lax - :http-only true - :path "/" - :max-age 1209600}} - (-> (mw.session/set-session-cookie {} {} uuid) - :cookies))) - -;; if `MB_SESSION_COOKIES=true` we shouldn't set a `Max-Age` -(let [uuid (UUID/randomUUID)] - (expect - {:value (str uuid) - :same-site :lax - :http-only true - :path "/"} - (let [env env/env] - (with-redefs [env/env (assoc env :mb-session-cookies "true")] - (-> (mw.session/set-session-cookie {} {} uuid) - (get-in [:cookies "metabase.SESSION"])))))) + (:import java.util.UUID)) + +(deftest set-session-cookie-test + (let [uuid (UUID/randomUUID)] + (testing "should unset the old SESSION_ID if it's present" + (is (= {"metabase.SESSION_ID" + {:value nil + :expires "Thu, 1 Jan 1970 00:00:00 GMT" + :path "/"} + "metabase.SESSION" + {:value (str uuid) + :same-site :lax + :http-only true + :path "/" + :max-age 1209600}} + (-> (mw.session/set-session-cookie {} {} uuid) + :cookies)))) + (testing "if `MB_SESSION_COOKIES=true` we shouldn't set a `Max-Age`" + (is (= {:value (str uuid) + :same-site :lax + :http-only true + :path "/"} + (let [env env/env] + (with-redefs [env/env (assoc env :mb-session-cookies "true")] + (-> (mw.session/set-session-cookie {} {} uuid) + (get-in [:cookies "metabase.SESSION"]))))))))) ;; if request is an HTTPS request then we should set `:secure true`. There are several different headers we check for ;; this. Make sure they all work. -(defn- secure-cookie-for-headers? [headers] - (-> (mw.session/set-session-cookie {:headers headers} {} (UUID/randomUUID)) - (get-in [:cookies "metabase.SESSION" :secure]) - boolean)) - -(expect true (secure-cookie-for-headers? {"x-forwarded-proto" "https"})) -(expect false (secure-cookie-for-headers? {"x-forwarded-proto" "http"})) - -(expect true (secure-cookie-for-headers? {"x-forwarded-protocol" "https"})) -(expect false (secure-cookie-for-headers? {"x-forwarded-protocol" "http"})) - -(expect true (secure-cookie-for-headers? {"x-url-scheme" "https"})) -(expect false (secure-cookie-for-headers? {"x-url-scheme" "http"})) - -(expect true (secure-cookie-for-headers? {"x-forwarded-ssl" "on"})) -(expect false (secure-cookie-for-headers? {"x-forwarded-ssl" "off"})) - -(expect true (secure-cookie-for-headers? {"front-end-https" "on"})) -(expect false (secure-cookie-for-headers? {"front-end-https" "off"})) - -(expect true (secure-cookie-for-headers? {"origin" "https://mysite.com"})) -(expect false (secure-cookie-for-headers? {"origin" "http://mysite.com"})) +(deftest secure-cookie-test + (doseq [[headers expected] [[{"x-forwarded-proto" "https"} true] + [{"x-forwarded-proto" "http"} false] + [{"x-forwarded-protocol" "https"} true] + [{"x-forwarded-protocol" "http"} false] + [{"x-url-scheme" "https"} true] + [{"x-url-scheme" "http"} false] + [{"x-forwarded-ssl" "on"} true] + [{"x-forwarded-ssl" "off"} false] + [{"front-end-https" "on"} true] + [{"front-end-https" "off"} false] + [{"origin" "https://mysite.com"} true] + [{"origin" "http://mysite.com"} false]]] + (let [actual (-> (mw.session/set-session-cookie {:headers headers} {} (UUID/randomUUID)) + (get-in [:cookies "metabase.SESSION" :secure]) + boolean)] + (is (= expected + actual) + (format "With headers %s we %s set the 'secure' attribute on the session cookie" + (pr-str headers) (if expected "SHOULD" "SHOULD NOT")))))) + +(deftest session-expired-test + (testing "Session expiration time = 1 minute" + (doseq [[created-at expected msg] + [[nil true "nil created-at"] + [(t/offset-date-time) false "brand-new session"] + [#t "1970-01-01T00:00:00Z" true "really old session"] + [(t/instant (- (System/currentTimeMillis) 61000)) true "session that is 61 seconds old"] + [(t/instant (- (System/currentTimeMillis) 59000)) false "session that is 59 seconds old"]]] + (is (= expected + (#'mw.session/session-expired? {:created_at created-at} 1)) + (format "%s %s be expired." msg (if expected "SHOULD" "SHOULD NOT")))))) ;;; ---------------------------------------- TEST wrap-session-id middleware ----------------------------------------- diff --git a/test/metabase/models/dependency_test.clj b/test/metabase/models/dependency_test.clj index 3e07ac8e12f..7add2b47151 100644 --- a/test/metabase/models/dependency_test.clj +++ b/test/metabase/models/dependency_test.clj @@ -5,7 +5,6 @@ [metabase.test [fixtures :as fixtures] [util :as tu]] - [metabase.util.date :as du] [toucan [db :as db] [models :as models]] @@ -55,12 +54,12 @@ :model_id 4 :dependent_on_model "test" :dependent_on_id 1 - :created_at (du/new-sql-timestamp)}] + :created_at :%now}] Dependency [_ {:model "Mock" :model_id 4 :dependent_on_model "foobar" :dependent_on_id 13 - :created_at (du/new-sql-timestamp)}]] + :created_at :%now}]] (format-dependencies (dep/retrieve-dependencies Mock 4)))) @@ -105,7 +104,7 @@ :model_id 1 :dependent_on_model "test" :dependent_on_id 5 - :created_at (du/new-sql-timestamp)}] + :created_at :%now}] (tu/with-model-cleanup [Dependency] (dep/update-dependencies! Mock 1 {:test [1 2]}) (format-dependencies (db/select Dependency, :model "Mock", :model_id 1))))) diff --git a/test/metabase/models/session_test.clj b/test/metabase/models/session_test.clj index 333389d969e..edfe1368823 100644 --- a/test/metabase/models/session_test.clj +++ b/test/metabase/models/session_test.clj @@ -1,16 +1,12 @@ (ns metabase.models.session-test - (:require [expectations :refer :all] - [metabase.models - [session :refer :all] - [user :refer [User]]] + (:require [clojure.test :refer :all] + [metabase.models :refer [Session User]] + [metabase.models.session :as session] [metabase.test.util :as tu] - [metabase.util.date :as du] [toucan.db :as db] [toucan.util.test :as tt])) -;; first-session-for-user -(expect - "the-greatest-day-ever" +(deftest first-session-for-user-test (tt/with-temp User [{user-id :id} {:first_name (tu/random-name) :last_name (tu/random-name) :email (str (tu/random-name) "@metabase.com") @@ -18,17 +14,18 @@ (db/simple-insert-many! Session [{:id "the-greatest-day-ever" :user_id user-id - :created_at (du/->Timestamp #inst "1980-10-19T05:05:05.000Z")} + :created_at #t "1980-10-19T05:05:05.000Z"} {:id "even-more-greatness" :user_id user-id - :created_at (du/->Timestamp #inst "1980-10-19T05:08:05.000Z")} + :created_at #t "1980-10-19T05:08:05.000Z"} {:id "the-world-of-bi-changes-forever" :user_id user-id - :created_at (du/->Timestamp #inst "2015-10-21")} + :created_at #t "2015-10-21"} {:id "something-could-have-happened" :user_id user-id - :created_at (du/->Timestamp #inst "1999-12-31")} + :created_at #t "1999-12-31"} {:id "now" :user_id user-id - :created_at (du/new-sql-timestamp)}]) - (first-session-for-user user-id))) + :created_at :%now}]) + (is (= "the-greatest-day-ever" + (session/first-session-for-user user-id))))) diff --git a/test/metabase/models/setting/cache_test.clj b/test/metabase/models/setting/cache_test.clj index ff2c625f99c..e8b740818b7 100644 --- a/test/metabase/models/setting/cache_test.clj +++ b/test/metabase/models/setting/cache_test.clj @@ -1,5 +1,6 @@ (ns metabase.models.setting.cache-test (:require [clojure.core.memoize :as memoize] + [clojure.test :refer :all] [expectations :refer [expect]] [honeysql.core :as hsql] [metabase @@ -9,9 +10,13 @@ [setting :refer [Setting]] [setting-test :as setting-test]] [metabase.models.setting.cache :as cache] - [metabase.test.util :as tu] + [metabase.test + [fixtures :as fixtures] + [util :as tu]] [toucan.db :as db])) +(use-fixtures :once (fixtures/initialize :db)) + ;;; --------------------------------------------- Cache Synchronization ---------------------------------------------- (defn- clear-cache! [] diff --git a/test/metabase/models/setting_test.clj b/test/metabase/models/setting_test.clj index 6c1b7ff30ed..b5815e24c49 100644 --- a/test/metabase/models/setting_test.clj +++ b/test/metabase/models/setting_test.clj @@ -420,7 +420,7 @@ (-> (db/query {:select [:value] :from [:setting] :where [:= :key (name setting-key)]}) - first :value u/jdbc-clob->str)) + first :value)) ;; If encryption is *enabled*, make sure Settings get saved as encrypted! (expect @@ -451,13 +451,13 @@ :type :timestamp) (expect - java.sql.Timestamp + java.time.temporal.Temporal (:tag (meta #'test-timestamp-setting))) ;; make sure we can set & fetch the value and that it gets serialized/deserialized correctly (expect - #inst "2018-07-11T09:32:00.000Z" - (do (test-timestamp-setting #inst "2018-07-11T09:32:00.000Z") + #t "2018-07-11T09:32:00.000Z" + (do (test-timestamp-setting #t "2018-07-11T09:32:00.000Z") (test-timestamp-setting))) diff --git a/test/metabase/models/task_history_test.clj b/test/metabase/models/task_history_test.clj index e38f43ba185..5b99e451cb8 100644 --- a/test/metabase/models/task_history_test.clj +++ b/test/metabase/models/task_history_test.clj @@ -1,38 +1,37 @@ (ns metabase.models.task-history-test - (:require [clj-time.core :as time] - [expectations :refer :all] + (:require [expectations :refer :all] + [java-time :as t] [metabase.models.task-history :refer :all] [metabase.test.util :as tu] [metabase.util :as u] - [metabase.util.date :as du] [toucan.db :as db] [toucan.util.test :as tt])) (defn add-second "Adds one second to `t`" [t] - (time/plus t (time/seconds 1))) + (t/plus t (t/seconds 1))) (defn add-10-millis "Adds 10 milliseconds to `t`" [t] - (time/plus t (time/millis 10))) + (t/plus t (t/millis 10))) (defn make-10-millis-task "Creates a map suitable for a `with-temp*` call for `TaskHistory`. Uses the `started_at` param sets the `ended_at` to 10 milliseconds later" [started-at] (let [ended-at (add-10-millis started-at)] - {:started_at (du/->Timestamp started-at) - :ended_at (du/->Timestamp ended-at) - :duration (du/calculate-duration started-at ended-at)})) + {:started_at started-at + :ended_at ended-at + :duration (.between java.time.temporal.ChronoUnit/MILLIS started-at ended-at)})) ;; Basic cleanup test where older rows are deleted and newer rows kept (let [task-4 (tu/random-name) task-5 (tu/random-name)] (expect #{task-4 task-5} - (let [t1-start (time/now) + (let [t1-start (t/zoned-date-time) t2-start (add-second t1-start) t3-start (add-second t2-start) t4-start (add-second t3-start) @@ -57,7 +56,7 @@ (expect [#{task-1 task-2} #{task-1 task-2}] - (let [t1-start (time/now) + (let [t1-start (t/zoned-date-time) t2-start (add-second t1-start)] (tt/with-temp* [TaskHistory [t1 (assoc (make-10-millis-task t1-start) :task task-1)] diff --git a/test/metabase/pulse/render/body_test.clj b/test/metabase/pulse/render/body_test.clj index d903fbd8e58..f79532b612a 100644 --- a/test/metabase/pulse/render/body_test.clj +++ b/test/metabase/pulse/render/body_test.clj @@ -1,14 +1,15 @@ (ns metabase.pulse.render.body-test - (:require [clojure.walk :as walk] + (:require [clojure + [test :refer :all] + [walk :as walk]] [expectations :refer [expect]] [hiccup.core :refer [html]] [metabase.pulse.render [body :as body] [common :as common] - [test-util :as render.tu]]) - (:import java.util.TimeZone)) + [test-util :as render.tu]])) -(def ^:private pacific-tz (TimeZone/getTimeZone "America/Los_Angeles")) +(def ^:private pacific-tz "America/Los_Angeles") (def ^:private test-columns [{:name "ID", @@ -316,37 +317,35 @@ :base_type :type/BigInteger :special_type nil}]) -;; Render a bar graph with non-nil values for the x and y axis -(expect - [true true] - (let [result (render-bar-graph {:cols default-columns - :rows [[10.0 1] [5.0 10] [2.50 20] [1.25 30]]})] - [(some #(= "Price" %) result) - (some #(= "NumPurchased" %) result)])) - -;; Check to make sure we allow nil values for the y-axis -(expect - [true true] - (let [result (render-bar-graph {:cols default-columns - :rows [[10.0 1] [5.0 10] [2.50 20] [1.25 nil]]})] - [(some #(= "Price" %) result) - (some #(= "NumPurchased" %) result)])) - -;; Check to make sure we allow nil values for the y-axis -(expect - [true true] - (let [result (render-bar-graph {:cols default-columns - :rows [[10.0 1] [5.0 10] [2.50 20] [nil 30]]})] - [(some #(= "Price" %) result) - (some #(= "NumPurchased" %) result)])) - -;; Check to make sure we allow nil values for both x and y on different rows -(expect - [true true] - (let [result (render-bar-graph {:cols default-columns - :rows [[10.0 1] [5.0 10] [nil 20] [1.25 nil]]})] - [(some #(= "Price" %) result) - (some #(= "NumPurchased" %) result)])) +(deftest render-bar-graph-test + (testing "Render a bar graph with non-nil values for the x and y axis" + (let [result (render-bar-graph {:cols default-columns + :rows [[10.0 1] [5.0 10] [2.50 20] [1.25 30]]})] + (is (= true + (some #(= "Price" %) result))) + (is (= true + (some #(= "NumPurchased" %) result))))) + (testing "Check to make sure we allow nil values for the y-axis" + (let [result (render-bar-graph {:cols default-columns + :rows [[10.0 1] [5.0 10] [2.50 20] [1.25 nil]]})] + (is (= true + (some #(= "Price" %) result))) + (is (= true + (some #(= "NumPurchased" %) result))))) + (testing "Check to make sure we allow nil values for the y-axis" + (let [result (render-bar-graph {:cols default-columns + :rows [[10.0 1] [5.0 10] [2.50 20] [nil 30]]})] + (is (= true + (some #(= "Price" %) result))) + (is (= true + (some #(= "NumPurchased" %) result))))) + (testing "Check to make sure we allow nil values for both x and y on different rows" + (let [result (render-bar-graph {:cols default-columns + :rows [[10.0 1] [5.0 10] [nil 20] [1.25 nil]]})] + (is (= true + (some #(= "Price" %) result))) + (is (= true + (some #(= "NumPurchased" %) result)))))) ;; Test rendering a sparkline ;; @@ -359,37 +358,29 @@ :attachments count)) -;; Test that we can render a sparkline with all valid values -(expect - 1 - (render-sparkline - {:cols default-columns - :rows [[10.0 1] [5.0 10] [2.50 20] [1.25 30]]})) - -;; Tex that we can have a nil value in the middle -(expect - 1 - (render-sparkline - {:cols default-columns - :rows [[10.0 1] [11.0 2] [5.0 nil] [2.50 20] [1.25 30]]})) - -;; Test that we can have a nil value for the y-axis at the end of the results -(expect - 1 - (render-sparkline - {:cols default-columns - :rows [[10.0 1] [11.0 2] [2.50 20] [1.25 nil]]})) - -;; Test that we can have a nil value for the x-axis at the end of the results -(expect - 1 - (render-sparkline - {:cols default-columns - :rows [[10.0 1] [11.0 2] [nil 20] [1.25 30]]})) - -;; Test that we can have a nil value for both x and y axis for different rows -(expect - 1 - (render-sparkline - {:cols default-columns - :rows [[10.0 1] [11.0 2] [nil 20] [1.25 nil]]})) +(deftest render-sparkline-test + (testing "Test that we can render a sparkline with all valid values" + (is (= 1 + (render-sparkline + {:cols default-columns + :rows [[10.0 1] [5.0 10] [2.50 20] [1.25 30]]})))) + (testing "Tex that we can have a nil value in the middle" + (is (= 1 + (render-sparkline + {:cols default-columns + :rows [[10.0 1] [11.0 2] [5.0 nil] [2.50 20] [1.25 30]]})))) + (testing "Test that we can have a nil value for the y-axis at the end of the results" + (is (= 1 + (render-sparkline + {:cols default-columns + :rows [[10.0 1] [11.0 2] [2.50 20] [1.25 nil]]})))) + (testing "Test that we can have a nil value for the x-axis at the end of the results" + (is (= 1 + (render-sparkline + {:cols default-columns + :rows [[10.0 1] [11.0 2] [nil 20] [1.25 30]]})))) + (testing "Test that we can have a nil value for both x and y axis for different rows" + (is (= 1 + (render-sparkline + {:cols default-columns + :rows [[10.0 1] [11.0 2] [nil 20] [1.25 nil]]}))))) diff --git a/test/metabase/pulse/render/datetime_test.clj b/test/metabase/pulse/render/datetime_test.clj index b8e808bc059..70b60d92b76 100644 --- a/test/metabase/pulse/render/datetime_test.clj +++ b/test/metabase/pulse/render/datetime_test.clj @@ -1,80 +1,46 @@ (ns metabase.pulse.render.datetime-test - (:require [clj-time.core :as t] - [expectations :refer [expect]] - [metabase.pulse.render.datetime :as datetime] - [metabase.util.date :as du]) - (:import java.util.TimeZone)) + (:require [clojure.test :refer :all] + [java-time :as t] + [metabase.pulse.render.datetime :as datetime])) -(def ^:private now "2020-07-16T18:04:00Z") +(def ^:private now "2020-07-16T18:04:00Z[UTC]") -(defn- utc [] (TimeZone/getTimeZone "UTC")) - -(defn- format-timestamp-pair +(defn- format-temporal-string-pair [unit datetime-str-1 datetime-str-2] - (with-redefs [t/now (constantly (du/str->date-time now (utc)))] - (datetime/format-timestamp-pair (utc) [datetime-str-1 datetime-str-2] {:unit unit}))) - -;; check that we can render relative timestamps for the various units we support + (t/with-clock (t/mock-clock (t/zoned-date-time now) (t/zone-id "UTC")) + (datetime/format-temporal-string-pair "UTC" [datetime-str-1 datetime-str-2] {:unit unit}))) ;; I don't know what exactly this is used for but we should at least make sure it's working correctly, see (#10326) - -(expect - ["Yesterday" "Previous day"] - (format-timestamp-pair :day "2020-07-15T18:04:00Z" nil)) - -(expect - ["Today" "Previous day"] - (format-timestamp-pair :day now nil)) - -(expect - ["Jul 18, 2020" "Jul 20, 2020"] - (format-timestamp-pair :day "2020-07-18T18:04:00Z" "2020-07-20T18:04:00Z")) - -(expect - ["Last week" "Previous week"] - (format-timestamp-pair :week "2020-07-09T18:04:00Z" nil)) - -(expect - ["This week" "Previous week"] - (format-timestamp-pair :week now nil)) - -(expect - ["Week 5 - 2020" "Week 13 - 2020"] - (format-timestamp-pair :week "2020-02-01T18:04:00Z" "2020-03-25T18:04:00Z")) - -(expect - ["This month" "Previous month"] - (format-timestamp-pair :month "2020-07-16T18:04:00Z" nil)) - -(expect - ["This month" "Previous month"] - (format-timestamp-pair :month now nil)) - -(expect - ["July 2021" "July 2022"] - (format-timestamp-pair :month "2021-07-16T18:04:00Z" "2022-07-16T18:04:00Z")) - -(expect - ["Last quarter" "Previous quarter"] - (format-timestamp-pair :quarter "2020-05-16T18:04:00Z" nil)) - -(expect - ["This quarter" "Previous quarter"] - (format-timestamp-pair :quarter now nil)) - -(expect - ["Q3 - 2018" "Q3 - 2019"] - (format-timestamp-pair :quarter "2018-07-16T18:04:00Z" "2019-07-16T18:04:00Z")) - -(expect - ["Last year" "Previous year"] - (format-timestamp-pair :year "2019-07-16T18:04:00Z" nil)) - -(expect - ["This year" "Previous year"] - (format-timestamp-pair :year now nil)) - -;; No special formatting for year? :shrug: -(expect - ["2018-07-16T18:04:00Z" "2021-07-16T18:04:00Z"] - (format-timestamp-pair :year "2018-07-16T18:04:00Z" "2021-07-16T18:04:00Z")) +(deftest format-temporal-string-pair-test + (testing "check that we can render relative timestamps for the various units we support" + (is (= ["Yesterday" "Previous day"] + (format-temporal-string-pair :day "2020-07-15T18:04:00Z" nil))) + (is (= ["Today" "Previous day"] + (format-temporal-string-pair :day now nil))) + (is (= ["Jul 18, 2020" "Jul 20, 2020"] + (format-temporal-string-pair :day "2020-07-18T18:04:00Z" "2020-07-20T18:04:00Z"))) + (is (= ["Last week" "Previous week"] + (format-temporal-string-pair :week "2020-07-09T18:04:00Z" nil))) + (is (= ["This week" "Previous week"] + (format-temporal-string-pair :week now nil))) + (is (= ["Week 5 - 2020" "Week 13 - 2020"] + (format-temporal-string-pair :week "2020-02-01T18:04:00Z" "2020-03-25T18:04:00Z"))) + (is (= ["This month" "Previous month"] + (format-temporal-string-pair :month "2020-07-16T18:04:00Z" nil))) + (is (= ["This month" "Previous month"] + (format-temporal-string-pair :month now nil))) + (is (= ["July 2021" "July 2022"] + (format-temporal-string-pair :month "2021-07-16T18:04:00Z" "2022-07-16T18:04:00Z"))) + (is (= ["Last quarter" "Previous quarter"] + (format-temporal-string-pair :quarter "2020-05-16T18:04:00Z" nil))) + (is (= ["This quarter" "Previous quarter"] + (format-temporal-string-pair :quarter now nil))) + (is (= ["Q3 - 2018" "Q3 - 2019"] + (format-temporal-string-pair :quarter "2018-07-16T18:04:00Z" "2019-07-16T18:04:00Z"))) + (is (= ["Last year" "Previous year"] + (format-temporal-string-pair :year "2019-07-16T18:04:00Z" nil))) + (is (= ["This year" "Previous year"] + (format-temporal-string-pair :year now nil))) + (testing "No special formatting for year? :shrug:" + (is (= ["2018-07-16T18:04:00Z" "2021-07-16T18:04:00Z"] + (format-temporal-string-pair :year "2018-07-16T18:04:00Z" "2021-07-16T18:04:00Z")))))) diff --git a/test/metabase/pulse/render/sparkline_test.clj b/test/metabase/pulse/render/sparkline_test.clj new file mode 100644 index 00000000000..e9c3965322d --- /dev/null +++ b/test/metabase/pulse/render/sparkline_test.clj @@ -0,0 +1,22 @@ +(ns metabase.pulse.render.sparkline-test + (:require [clojure.test :refer :all] + [java-time :as t] + [metabase.pulse.render.sparkline :as sparkline])) + +(deftest format-val-fn-test + "Make sure format-val-fn works correctly for all of the various temporal types" + (let [f (#'sparkline/format-val-fn "US/Pacific" nil (constantly {:base_type :type/DateTime})) + t #t "2019-11-20T20:09:55.752-08:00[America/Los_Angeles]"] + (doseq [t' [(t/instant t) + (t/local-date t) + (t/local-time t) + (t/offset-time t) + (t/offset-time t) + (t/local-date-time t) + (t/offset-date-time t) + (t/offset-date-time t) + t] + x [t' (str t')]] + (testing (format "^%s %s" (.getName (class x)) x) + (is (= true + (boolean (f x)))))))) diff --git a/test/metabase/pulse_test.clj b/test/metabase/pulse_test.clj index 2e95969ecc8..f7676e5b929 100644 --- a/test/metabase/pulse_test.clj +++ b/test/metabase/pulse_test.clj @@ -539,7 +539,7 @@ (output (var-get #'render.body/attached-results-text))])))) (defn- produces-bytes? [{:keys [attachment-bytes-thunk]}] - (< 0 (alength (attachment-bytes-thunk)))) + (< 0 (alength ^bytes (attachment-bytes-thunk)))) ;; Basic slack test, 2 cards, 1 recipient channel (tt/expect-with-temp [Card [{card-id-1 :id} (checkins-query {:breakout [["datetime-field" (data/id :checkins :date) "hour"]]})] @@ -578,12 +578,12 @@ [(thunk->boolean slack-data) (every? produces-bytes? (:attachments slack-data))]))) -(defn- email-body? [{message-type :type content :content}] +(defn- email-body? [{message-type :type, ^String content :content}] (and (= "text/html; charset=utf-8" message-type) (string? content) (.startsWith content "<html>"))) -(defn- attachment? [{message-type :type content-type :content-type content :content}] +(defn- attachment? [{message-type :type, content-type :content-type, content :content}] (and (= :inline message-type) (= "image/png" content-type) (instance? java.net.URL content))) diff --git a/test/metabase/query_processor/middleware/add_dimension_projections_test.clj b/test/metabase/query_processor/middleware/add_dimension_projections_test.clj index d4023f053af..1cad06a20d9 100644 --- a/test/metabase/query_processor/middleware/add_dimension_projections_test.clj +++ b/test/metabase/query_processor/middleware/add_dimension_projections_test.clj @@ -1,8 +1,12 @@ (ns metabase.query-processor.middleware.add-dimension-projections-test - (:require [expectations :refer [expect]] + (:require [clojure.test :refer :all] + [expectations :refer [expect]] [metabase.query-processor.middleware.add-dimension-projections :as add-dim-projections] + [metabase.test.fixtures :as fixtures] [toucan.hydrate :as hydrate])) +(use-fixtures :once (fixtures/initialize :db)) + ;;; ----------------------------------------- add-fk-remaps (pre-processing) ----------------------------------------- (def ^:private example-query diff --git a/test/metabase/query_processor/middleware/optimize_datetime_filters_test.clj b/test/metabase/query_processor/middleware/optimize_datetime_filters_test.clj index 4e80e1f5058..df51407f8ae 100644 --- a/test/metabase/query_processor/middleware/optimize_datetime_filters_test.clj +++ b/test/metabase/query_processor/middleware/optimize_datetime_filters_test.clj @@ -22,6 +22,38 @@ :query {:filter filter-clause}}) (get-in [:query :filter]))) +(deftest optimize-day-bucketed-filter-test + (testing "Make sure we aren't doing anything wacky when optimzing filters against fields bucketed by day" + (letfn [(optimize [filter-type] + (#'optimize-datetime-filters/optimize-filter + [filter-type + [:datetime-field [:field-id 1] :day] + [:absolute-datetime (t/zoned-date-time "2014-03-04T12:30Z[UTC]") :day]]))] + (testing :< + (is (= [:< + [:datetime-field [:field-id 1] :default] + [:absolute-datetime (t/zoned-date-time "2014-03-04T00:00Z[UTC]") :default]] + (optimize :<)) + "day(field) < day('2014-03-04T12:30') => day(field) < '2014-03-04' => field < '2014-03-04T00:00'")) + (testing :<= + (is (= [:< + [:datetime-field [:field-id 1] :default] + [:absolute-datetime (t/zoned-date-time "2014-03-05T00:00Z[UTC]") :default]] + (optimize :<=)) + "day(field) <= day('2014-03-04T12:30') => day(field) <= '2014-03-04' => field < '2014-03-05T00:00'")) + (testing :> + (is (= [:>= + [:datetime-field [:field-id 1] :default] + [:absolute-datetime (t/zoned-date-time "2014-03-05T00:00Z[UTC]") :default]] + (optimize :>)) + "day(field) > day('2014-03-04T12:30') => day(field) > '2014-03-04' => field >= '2014-03-05T00:00'")) + (testing :>= + (is (= [:>= + [:datetime-field [:field-id 1] :default] + [:absolute-datetime (t/zoned-date-time "2014-03-04T00:00Z[UTC]") :default]] + (optimize :>=)) + "day(field) >= day('2014-03-04T12:30') => day(field) >= '2014-03-04' => field >= '2014-03-04T00:00'"))))) + (def ^:private test-units-and-values [{:unit :second :filter-value (u.date/parse "2019-09-24T12:19:30.500Z" "UTC") @@ -78,21 +110,31 @@ [:!= [:datetime-field [:field-id 1] unit] [:absolute-datetime filter-value unit]])))) - (doseq [filter-type [:< :<=]] - (testing filter-type - (is (= [filter-type [:datetime-field [:field-id 1] :default] lower] - (optimize-datetime-filters - [filter-type - [:datetime-field [:field-id 1] unit] - [:absolute-datetime filter-value unit]]))))) - (doseq [filter-type [:> :>=]] - (testing filter-type - (is (= [filter-type [:datetime-field [:field-id 1] :default] upper] - (optimize-datetime-filters - [filter-type - [:datetime-field [:field-id 1] unit] - [:absolute-datetime filter-value unit]]))))) - (testing :betweenn + (testing :< + (is (= [:< [:datetime-field [:field-id 1] :default] lower] + (optimize-datetime-filters + [:< + [:datetime-field [:field-id 1] unit] + [:absolute-datetime filter-value unit]])))) + (testing :<= + (is (= [:< [:datetime-field [:field-id 1] :default] upper] + (optimize-datetime-filters + [:<= + [:datetime-field [:field-id 1] unit] + [:absolute-datetime filter-value unit]])))) + (testing :> + (is (= [:>= [:datetime-field [:field-id 1] :default] upper] + (optimize-datetime-filters + [:> + [:datetime-field [:field-id 1] unit] + [:absolute-datetime filter-value unit]])))) + (testing :>= + (is (= [:>= [:datetime-field [:field-id 1] :default] lower] + (optimize-datetime-filters + [:>= + [:datetime-field [:field-id 1] unit] + [:absolute-datetime filter-value unit]])))) + (testing :between (is (= [:and [:>= [:datetime-field [:field-id 1] :default] lower] [:< [:datetime-field [:field-id 1] :default] upper]] @@ -136,7 +178,7 @@ t timezone-id lower upper)))))))))) (deftest skip-optimization-test - (let [clause [:= [:datetime-field [:field-id 1] :day] [:absolute-datetime #inst "2019-01-01" :month]]] + (let [clause [:= [:datetime-field [:field-id 1] :day] [:absolute-datetime #t "2019-01-01" :month]]] (is (= clause (optimize-datetime-filters clause)) "Filters with different units in the datetime field and absolute-datetime shouldn't get optimized"))) diff --git a/test/metabase/query_processor/middleware/parameters/date_test.clj b/test/metabase/query_processor/middleware/parameters/date_test.clj index 2117dd72234..16a75d49bdc 100644 --- a/test/metabase/query_processor/middleware/parameters/date_test.clj +++ b/test/metabase/query_processor/middleware/parameters/date_test.clj @@ -1,51 +1,49 @@ (ns metabase.query-processor.middleware.parameters.date-test "Tests for datetime parameters." - (:require [clj-time.core :as t] - [expectations :refer :all] - [metabase.query-processor.middleware.parameters.dates :refer :all])) + (:require [clojure.test :refer :all] + [java-time :as t] + [metabase.query-processor.middleware.parameters.dates :as dates])) -;; we hard code "now" to a specific point in time so that we can control the test output -(defn- test-date->range [value] - (with-redefs [t/now (constantly (t/date-time 2016 06 07 12 0 0))] - (date-string->range value nil))) - -(expect {:end "2016-03-31", :start "2016-01-01"} (test-date->range "Q1-2016")) -(expect {:end "2016-02-29", :start "2016-02-01"} (test-date->range "2016-02")) -(expect {:end "2016-04-18", :start "2016-04-18"} (test-date->range "2016-04-18")) -(expect {:end "2016-04-23", :start "2016-04-18"} (test-date->range "2016-04-18~2016-04-23")) -(expect {:end "2016-04-23", :start "2016-04-18"} (test-date->range "2016-04-18~2016-04-23")) -(expect {:start "2016-04-18"} (test-date->range "2016-04-18~")) -(expect {:end "2016-04-18"} (test-date->range "~2016-04-18")) - -(expect {:end "2016-06-06", :start "2016-06-04"} (test-date->range "past3days")) -(expect {:end "2016-06-07", :start "2016-06-04"} (test-date->range "past3days~")) -(expect {:end "2016-06-06", :start "2016-05-31"} (test-date->range "past7days")) -(expect {:end "2016-06-06", :start "2016-05-08"} (test-date->range "past30days")) -(expect {:end "2016-05-31", :start "2016-04-01"} (test-date->range "past2months")) -(expect {:end "2016-06-30", :start "2016-04-01"} (test-date->range "past2months~")) -(expect {:end "2016-05-31", :start "2015-05-01"} (test-date->range "past13months")) -(expect {:end "2015-12-31", :start "2015-01-01"} (test-date->range "past1years")) -(expect {:end "2016-12-31", :start "2015-01-01"} (test-date->range "past1years~")) -(expect {:end "2015-12-31", :start "2000-01-01"} (test-date->range "past16years")) - -(expect {:end "2016-06-10", :start "2016-06-08"} (test-date->range "next3days")) -(expect {:end "2016-06-10", :start "2016-06-07"} (test-date->range "next3days~")) -(expect {:end "2016-06-14", :start "2016-06-08"} (test-date->range "next7days")) -(expect {:end "2016-07-07", :start "2016-06-08"} (test-date->range "next30days")) -(expect {:end "2016-08-31", :start "2016-07-01"} (test-date->range "next2months")) -(expect {:end "2016-08-31", :start "2016-06-01"} (test-date->range "next2months~")) -(expect {:end "2017-07-31", :start "2016-07-01"} (test-date->range "next13months")) -(expect {:end "2017-12-31", :start "2017-01-01"} (test-date->range "next1years")) -(expect {:end "2017-12-31", :start "2016-01-01"} (test-date->range "next1years~")) -(expect {:end "2032-12-31", :start "2017-01-01"} (test-date->range "next16years")) - -(expect {:end "2016-06-07", :start "2016-06-07"} (test-date->range "thisday")) -(expect {:end "2016-06-11", :start "2016-06-05"} (test-date->range "thisweek")) -(expect {:end "2016-06-30", :start "2016-06-01"} (test-date->range "thismonth")) -(expect {:end "2016-12-31", :start "2016-01-01"} (test-date->range "thisyear")) - -(expect {:end "2016-06-04", :start "2016-05-29"} (test-date->range "lastweek")) -(expect {:end "2016-05-31", :start "2016-05-01"} (test-date->range "lastmonth")) -(expect {:end "2015-12-31", :start "2015-01-01"} (test-date->range "lastyear")) -(expect {:end "2016-06-06", :start "2016-06-06"} (test-date->range "yesterday")) -(expect {:end "2016-06-07", :start "2016-06-07"} (test-date->range "today")) +(deftest date-string->range-test + (t/with-clock (t/mock-clock #t "2016-06-07T12:00Z") + (doseq [[group s->expected] + {"absolute datetimes" {"Q1-2016" {:end "2016-03-31", :start "2016-01-01"} + "2016-02" {:end "2016-02-29", :start "2016-02-01"} + "2016-04-18" {:end "2016-04-18", :start "2016-04-18"} + "2016-04-18~2016-04-23" {:end "2016-04-23", :start "2016-04-18"} + "2016-04-18~" {:start "2016-04-18"} + "~2016-04-18" {:end "2016-04-18"}} + "relative (past)" {"past3days" {:end "2016-06-06", :start "2016-06-04"} + "past3days~" {:end "2016-06-07", :start "2016-06-04"} + "past7days" {:end "2016-06-06", :start "2016-05-31"} + "past30days" {:end "2016-06-06", :start "2016-05-08"} + "past2months" {:end "2016-05-31", :start "2016-04-01"} + "past2months~" {:end "2016-06-30", :start "2016-04-01"} + "past13months" {:end "2016-05-31", :start "2015-05-01"} + "past1years" {:end "2015-12-31", :start "2015-01-01"} + "past1years~" {:end "2016-12-31", :start "2015-01-01"} + "past16years" {:end "2015-12-31", :start "2000-01-01"}} + "relative (next)" {"next3days" {:end "2016-06-10", :start "2016-06-08"} + "next3days~" {:end "2016-06-10", :start "2016-06-07"} + "next7days" {:end "2016-06-14", :start "2016-06-08"} + "next30days" {:end "2016-07-07", :start "2016-06-08"} + "next2months" {:end "2016-08-31", :start "2016-07-01"} + "next2months~" {:end "2016-08-31", :start "2016-06-01"} + "next13months" {:end "2017-07-31", :start "2016-07-01"} + "next1years" {:end "2017-12-31", :start "2017-01-01"} + "next1years~" {:end "2017-12-31", :start "2016-01-01"} + "next16years" {:end "2032-12-31", :start "2017-01-01"}} + "relative (this)" {"thisday" {:end "2016-06-07", :start "2016-06-07"} + "thisweek" {:end "2016-06-11", :start "2016-06-05"} + "thismonth" {:end "2016-06-30", :start "2016-06-01"} + "thisyear" {:end "2016-12-31", :start "2016-01-01"}} + "relative (last)" {"lastweek" {:end "2016-06-04", :start "2016-05-29"} + "lastmonth" {:end "2016-05-31", :start "2016-05-01"} + "lastyear" {:end "2015-12-31", :start "2015-01-01"}} + "relative (today/yesterday)" {"yesterday" {:end "2016-06-06", :start "2016-06-06"} + "today" {:end "2016-06-07", :start "2016-06-07"}}}] + (testing group + (doseq [[s expected] s->expected] + (is (= expected + (dates/date-string->range s nil)) + (format "%s should parse to %s" (pr-str s) (pr-str expected)))))))) diff --git a/test/metabase/query_processor/middleware/parameters/native_test.clj b/test/metabase/query_processor/middleware/parameters/native_test.clj index f6349750ccd..204a6275656 100644 --- a/test/metabase/query_processor/middleware/parameters/native_test.clj +++ b/test/metabase/query_processor/middleware/parameters/native_test.clj @@ -1,11 +1,8 @@ (ns metabase.query-processor.middleware.parameters.native-test - "E2E tests for SQL param substitution. - - TIMEZONE FIXME - we should rework this namespace to use `java-time ` instead of `clj-time`." - (:require [clj-time.core :as t] - [clojure.test :refer :all] + "E2E tests for SQL param substitution." + (:require [clojure.test :refer :all] [expectations :refer [expect]] - [java-time :as jt] + [java-time :as t] [metabase [driver :as driver] [query-processor :as qp] @@ -20,9 +17,7 @@ [data :as data] [util :as tu]] [metabase.test.data.datasets :as datasets] - [metabase.util - [date :as du] - [schema :as su]] + [metabase.util.schema :as su] [schema.core :as s])) ;;; ------------------------------------------ simple substitution — {{x}} ------------------------------------------ @@ -198,15 +193,12 @@ ;;; ------------------------------------------- expansion tests: variables ------------------------------------------- -(defmacro ^:private with-h2-db-timezone +;; REMOVE ME ? +(defmacro ^:deprecated ^:private with-h2-db-timezone "This macro is useful when testing pieces of the query pipeline (such as expand) where it's a basic unit test not involving a database, but does need to parse dates" [& body] - `(du/with-effective-timezone {:engine :h2 - :timezone "UTC" - :name "mock_db" - :id 1} - ~@body)) + `(do ~@body)) (defn- expand** "Expand parameters inside a top-level native `query`. Not recursive. " @@ -257,7 +249,7 @@ ;; specified param (date/single) (expect {:query "SELECT * FROM orders WHERE created_at > ?;" - :params [(jt/local-date "2016-07-19")]} + :params [#t "2016-07-19"]} (expand* {:native {:query "SELECT * FROM orders WHERE created_at > {{created_at}};" :template-tags {"created_at" {:name "created_at", :display-name "Created At", :type "date"}}} :parameters [{:type :date/single, :target [:variable [:template-tag "created_at"]], :value "2016-07-19"}]})) @@ -279,8 +271,9 @@ ([sql field-filter-param] ;; TIMEZONE FIXME - (with-redefs [t/now (constantly (t/date-time 2016 06 07 12 0 0))] - (-> {:native {:query sql + (t/with-clock (t/mock-clock #t "2016-06-07T12:00-00:00" (t/zone-id "UTC")) + (-> {:native {:query + sql :template-tags {"date" {:name "date" :display-name "Checkin Date" :type :dimension @@ -294,74 +287,74 @@ (deftest expand-field-filters-test (testing "dimension (date/single)" (is (= {:query "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) = ?;" - :params [(jt/local-date "2016-07-01")]} + :params [#t "2016-07-01"]} (expand-with-field-filter-param {:type :date/single, :value "2016-07-01"})))) (testing "dimension (date/range)" (is (= {:query "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ?;" - :params [(jt/local-date "2016-07-01") - (jt/local-date "2016-08-01")]} + :params [#t "2016-07-01" + #t "2016-08-01"]} (expand-with-field-filter-param {:type :date/range, :value "2016-07-01~2016-08-01"})))) (testing "dimension (date/month-year)" (is (= {:query "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ?;" - :params [(jt/local-date "2016-07-01") - (jt/local-date "2016-07-31")]} + :params [#t "2016-07-01" + #t "2016-07-31"]} (expand-with-field-filter-param {:type :date/month-year, :value "2016-07"})))) (testing "dimension (date/quarter-year)" (is (= {:query "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ?;" - :params [(jt/local-date "2016-01-01") - (jt/local-date "2016-03-31")]} + :params [#t "2016-01-01" + #t "2016-03-31"]} (expand-with-field-filter-param {:type :date/quarter-year, :value "Q1-2016"})))) (testing "dimension (date/all-options, before)" (is (= {:query "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) < ?;" - :params [(jt/local-date "2016-07-01")]} + :params [#t "2016-07-01"]} (expand-with-field-filter-param {:type :date/all-options, :value "~2016-07-01"})))) (testing "dimension (date/all-options, after)" (is (= {:query "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) > ?;" - :params [(jt/local-date "2016-07-01")]} + :params [#t "2016-07-01"]} (expand-with-field-filter-param {:type :date/all-options, :value "2016-07-01~"})))) (testing "relative date — 'yesterday'" (is (= {:query "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) = ?;" - :params [(jt/local-date "2016-06-06")]} + :params [#t "2016-06-06"]} (expand-with-field-filter-param {:type :date/range, :value "yesterday"})))) (testing "relative date — 'past7days'" (is (= {:query "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ?;" - :params [(jt/local-date "2016-05-31") - (jt/local-date "2016-06-06")]} + :params [#t "2016-05-31" + #t "2016-06-06"]} (expand-with-field-filter-param {:type :date/range, :value "past7days"})))) (testing "relative date — 'past30days'" (is (= {:query "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ?;" - :params [(jt/local-date "2016-05-08") - (jt/local-date "2016-06-06")]} + :params [#t "2016-05-08" + #t "2016-06-06"]} (expand-with-field-filter-param {:type :date/range, :value "past30days"})))) (testing "relative date — 'thisweek'" (is (= {:query "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ?;" - :params [(jt/local-date "2016-06-05") - (jt/local-date "2016-06-11")]} + :params [#t "2016-06-05" + #t "2016-06-11"]} (expand-with-field-filter-param {:type :date/range, :value "thisweek"})))) (testing "relative date — 'thismonth'" (is (= {:query "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ?;" - :params [(jt/local-date "2016-06-01") - (jt/local-date "2016-06-30")]} + :params [#t "2016-06-01" + #t "2016-06-30"]} (expand-with-field-filter-param {:type :date/range, :value "thismonth"})))) (testing "relative date — 'thisyear'" (is (= {:query "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ?;" - :params [(jt/local-date "2016-01-01") - (jt/local-date "2016-12-31")]} + :params [#t "2016-01-01" + #t "2016-12-31"]} (expand-with-field-filter-param {:type :date/range, :value "thisyear"})))) (testing "relative date — 'lastweek'" (is (= {:query "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ?;" - :params [(jt/local-date "2016-05-29") - (jt/local-date "2016-06-04")]} + :params [#t "2016-05-29" + #t "2016-06-04"]} (expand-with-field-filter-param {:type :date/range, :value "lastweek"})))) (testing "relative date — 'lastmonth'" (is (= {:query "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ?;" - :params [(jt/local-date "2016-05-01") - (jt/local-date "2016-05-31")]} + :params [#t "2016-05-01" + #t "2016-05-31"]} (expand-with-field-filter-param {:type :date/range, :value "lastmonth"})))) (testing "relative date — 'lastyear'" (is (= {:query "SELECT * FROM checkins WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ?;" - :params [(jt/local-date "2015-01-01") - (jt/local-date "2015-12-31")]} + :params [#t "2015-01-01" + #t "2015-12-31"]} (expand-with-field-filter-param {:type :date/range, :value "lastyear"})))) (testing "dimension with no value — just replace with an always true clause (e.g. 'WHERE 1 = 1')" (is (= {:query "SELECT * FROM checkins WHERE 1 = 1;" @@ -503,8 +496,8 @@ ;; Some random end-to-end param expansion tests added as part of the SQL Parameters 2.0 rewrite (deftest param-expansion-test (is (= {:query "SELECT count(*) FROM CHECKINS WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ?", - :params [(jt/local-date "2017-03-01") - (jt/local-date "2017-03-31")]} + :params [#t "2017-03-01" + #t "2017-03-31"]} (expand* {:native {:query "SELECT count(*) FROM CHECKINS WHERE {{created_at}}" :template-tags {"created_at" {:name "created_at" :display-name "Created At" @@ -564,9 +557,9 @@ "FROM CHECKINS " "WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ? " "GROUP BY \"DATE\"") - :params [(jt/local-date "2017-10-31") - (jt/local-date "2017-11-04")]} - (with-redefs [t/now (constantly (t/date-time 2017 11 05 12 0 0))] + :params [#t "2017-10-31" + #t "2017-11-04"]} + (t/with-clock (t/mock-clock #t "2017-11-05T12:00Z" (t/zone-id "UTC")) (expand* {:native {:query (str "SELECT count(*) AS \"count\", \"DATE\" " "FROM CHECKINS " "WHERE {{checkin_date}} " @@ -586,9 +579,9 @@ "FROM CHECKINS " "WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) BETWEEN ? AND ? " "GROUP BY \"DATE\"") - :params [(jt/local-date "2017-10-31") - (jt/local-date "2017-11-04")]} - (with-redefs [t/now (constantly (t/date-time 2017 11 05 12 0 0))] + :params [#t "2017-10-31" + #t "2017-11-04"]} + (t/with-clock (t/mock-clock #t "2017-11-05T12:00Z" (t/zone-id "UTC")) (expand* {:native {:query (str "SELECT count(*) AS \"count\", \"DATE\" " "FROM CHECKINS " "WHERE {{checkin_date}} " @@ -606,7 +599,7 @@ "FROM CHECKINS " "WHERE CAST(\"PUBLIC\".\"CHECKINS\".\"DATE\" AS date) = ? " "GROUP BY \"DATE\"") - :params [(jt/local-date "2017-11-14")]} + :params [#t "2017-11-14"]} (expand* {:native {:query (str "SELECT count(*) AS \"count\", \"DATE\" " "FROM CHECKINS " "WHERE {{checkin_date}} " diff --git a/test/metabase/query_processor/middleware/results_metadata_test.clj b/test/metabase/query_processor/middleware/results_metadata_test.clj index 85acc4782d5..fa93aa831a9 100644 --- a/test/metabase/query_processor/middleware/results_metadata_test.clj +++ b/test/metabase/query_processor/middleware/results_metadata_test.clj @@ -171,8 +171,8 @@ :name "DATE" :unit "year" :special_type nil - :fingerprint {:global {:distinct-count 618 :nil% 0.0}, :type {:type/DateTime {:earliest "2013-01-03T00:00:00.000Z" - :latest "2015-12-29T00:00:00.000Z"}}}} + :fingerprint {:global {:distinct-count 618 :nil% 0.0}, :type {:type/DateTime {:earliest "2013-01-03" + :latest "2015-12-29"}}}} {:base_type "type/Integer" :display_name "Count" :name "count" diff --git a/test/metabase/query_processor_test/date_bucketing_test.clj b/test/metabase/query_processor_test/date_bucketing_test.clj index b03a99adf35..8afb2f3362f 100644 --- a/test/metabase/query_processor_test/date_bucketing_test.clj +++ b/test/metabase/query_processor_test/date_bucketing_test.clj @@ -37,9 +37,7 @@ [datasets :as datasets] [interface :as tx]] [metabase.test.util.timezone :as tu.tz] - [metabase.util - [date :as du] - [date-2 :as u.date]] + [metabase.util.date-2 :as u.date] [potemkin.types :as p.types] [pretty.core :as pretty] [toucan.db :as db]) @@ -298,7 +296,7 @@ :else (sad-toucan-result (default-timezone-parse-fn :utc) (format-in-timezone-fn :pacific))) - (tu.tz/with-jvm-tz (timezone :pacific) + (tu.tz/with-system-timezone-id (timezone :pacific) (sad-toucan-incidents-with-bucketing :default :eastern))))))) (deftest group-by-minute-test @@ -566,7 +564,7 @@ (results-by-day (default-timezone-parse-fn :utc) (format-in-timezone-fn :pacific) [6 10 4 9 9 8 8 9 7 9])) - (tu.tz/with-jvm-tz (timezone :pacific) + (tu.tz/with-system-timezone-id (timezone :pacific) (sad-toucan-incidents-with-bucketing :day :pacific))))))) (deftest group-by-day-of-week-test @@ -750,7 +748,7 @@ (results-by-week u.date/parse (format-in-timezone-fn :pacific) [46 47 40 60 7])) - (tu.tz/with-jvm-tz (timezone :pacific) + (tu.tz/with-system-timezone-id (timezone :pacific) (sad-toucan-incidents-with-bucketing :week :pacific))))))) ;; TODO — Group by `:iso-week` test! @@ -865,28 +863,29 @@ [{:field-name "timestamp" :base-type :type/DateTime}] (vec (for [i (range -15 15)] + ;; TIMESTAMP FIXME — not sure if still needed + ;; ;; Create timestamps using relative dates (e.g. `DATEADD(second, -195, GETUTCDATE())` instead of - ;; generating `java.sql.Timestamps` here so they'll be in the DB's native timezone. Some DBs refuse to use + ;; generating Java classes here so they'll be in the DB's native timezone. Some DBs refuse to use ;; the same timezone we're running the tests from *cough* SQL Server *cough* [(u/prog1 (if (isa? driver/hierarchy driver/*driver* :sql) (driver/date-add driver/*driver* (sql.qp/current-datetime-fn driver/*driver*) (* i interval-seconds) :second) - (du/relative-date :second (* i interval-seconds))) + (u.date/add :second (* i interval-seconds))) (assert <>))]))]))) (defn- dataset-def-with-timestamps [interval-seconds] (TimestampDatasetDef. interval-seconds)) (def ^:private checkins:4-per-minute (dataset-def-with-timestamps 15)) -(def ^:private checkins:4-per-hour (dataset-def-with-timestamps (u.date/minutes->seconds 15))) -(def ^:private checkins:1-per-day (dataset-def-with-timestamps (* 24 (u.date/minutes->seconds 60)))) +(def ^:private checkins:4-per-hour (dataset-def-with-timestamps (u/minutes->seconds 15))) +(def ^:private checkins:1-per-day (dataset-def-with-timestamps (* 24 (u/minutes->seconds 60)))) (defn- checkins-db-is-old? [max-age-seconds] - (let [created-at (t/instant (:created_at (data/db))) - age (t/duration created-at (t/instant))] - (> (.getSeconds age) max-age-seconds))) + (u.date/greater-than-period-duration? (u.date/period-duration (:created_at (data/db)) (t/zoned-date-time)) + (t/seconds max-age-seconds))) (def ^:private ^:dynamic *recreate-db-if-stale?* true) @@ -1007,7 +1006,7 @@ (data/dataset checkins:1-per-day (data/run-mbql-query checkins {:aggregation [[:count]] - :filter [:= [:field-id $timestamp] (du/format-date "yyyy-MM-dd" (du/date-trunc :day))]}))))))) + :filter [:= [:field-id $timestamp] (t/format "yyyy-MM-dd" (u.date/truncate :day))]}))))))) ;; this is basically the same test as above, but using the office-checkins dataset instead of the dynamically ;; created checkins DBs so we can run it against Snowflake and BigQuery as well. (datasets/test-drivers (qp.test/normal-drivers) @@ -1037,7 +1036,7 @@ (qp.test/rows (data/run-mbql-query checkins {:aggregation [[:count]] - :filter [:= [:field-id $timestamp] (str (du/format-date "yyyy-MM-dd" (du/date-trunc :day)) + :filter [:= [:field-id $timestamp] (str (t/format "yyyy-MM-dd" (u.date/truncate :day)) "T14:16:00Z")]}))))))))) (def ^:private addition-unit-filtering-vals diff --git a/test/metabase/query_processor_test/expressions_test.clj b/test/metabase/query_processor_test/expressions_test.clj index 10c9d093ffc..a12afc96ebd 100644 --- a/test/metabase/query_processor_test/expressions_test.clj +++ b/test/metabase/query_processor_test/expressions_test.clj @@ -1,8 +1,6 @@ (ns metabase.query-processor-test.expressions-test "Tests for expressions (calculated columns)." - (:require [clj-time - [coerce :as tcoerce] - [core :as time]] + (:require [clj-time.core :as time] [clojure.test :refer :all] [java-time :as t] [metabase @@ -12,9 +10,7 @@ [data :as data] [util :as tu]] [metabase.test.data.datasets :as datasets] - [metabase.util - [date :as du] - [date-2 :as u.date]])) + [metabase.util.date-2 :as u.date])) ;; Do a basic query including an expression (datasets/expect-with-drivers (qp.test/normal-drivers-with-feature :expressions) @@ -30,17 +26,18 @@ :limit 5 :order-by [[:asc $id]]})))) -;; Make sure FLOATING POINT division is done -(datasets/expect-with-drivers (qp.test/normal-drivers-with-feature :expressions) - [[1 "Red Medicine" 4 10.0646 -165.374 3 1.5] ; 3 / 2 SHOULD BE 1.5, NOT 1 (!) - [2 "Stout Burgers & Beers" 11 34.0996 -118.329 2 1.0] - [3 "The Apple Pan" 11 34.0406 -118.428 2 1.0]] - (qp.test/format-rows-by [int str int 4.0 4.0 int float] - (qp.test/rows - (data/run-mbql-query venues - {:expressions {:my-cool-new-field [:/ $price 2]} - :limit 3 - :order-by [[:asc $id]]})))) +(deftest floating-point-division-test + (datasets/test-drivers (qp.test/normal-drivers-with-feature :expressions) + (is (= [[1 "Red Medicine" 4 10.0646 -165.374 3 1.5] ; 3 / 2 SHOULD BE 1.5, NOT 1 (!) + [2 "Stout Burgers & Beers" 11 34.0996 -118.329 2 1.0] + [3 "The Apple Pan" 11 34.0406 -118.428 2 1.0]] + (qp.test/format-rows-by [int str int 4.0 4.0 int float] + (qp.test/rows + (data/run-mbql-query venues + {:expressions {:my-cool-new-field [:/ $price 2]} + :limit 3 + :order-by [[:asc $id]]})))) + "Make sure FLOATING POINT division is done"))) ;; Can we do NESTED EXPRESSIONS ? (datasets/expect-with-drivers (qp.test/normal-drivers-with-feature :expressions) @@ -205,7 +202,7 @@ (defn- maybe-truncate [dt] (if (= :sqlite driver/*driver*) - (->> dt (du/date-trunc :day) tcoerce/from-sql-date) + (u.date/truncate dt :day) dt)) (defn- robust-dates diff --git a/test/metabase/query_processor_test/failure_test.clj b/test/metabase/query_processor_test/failure_test.clj index b5cde07163e..3f738896f3c 100644 --- a/test/metabase/query_processor_test/failure_test.clj +++ b/test/metabase/query_processor_test/failure_test.clj @@ -56,7 +56,7 @@ ;; running via `process-query-and-save-execution!` should return similar info and a bunch of other nonsense too (tu/expect-schema {:database_id (s/eq (data/id)) - :started_at java.util.Date + :started_at (s/eq :%now) :json_query (assoc-in (bad-query-schema) [:middleware :userland-query?] (s/eq true)) :native bad-query-native-schema :status (s/eq :failed) diff --git a/test/metabase/sync/analyze/classify_test.clj b/test/metabase/sync/analyze/classify_test.clj index eb035e456bf..01c93f27eb9 100644 --- a/test/metabase/sync/analyze/classify_test.clj +++ b/test/metabase/sync/analyze/classify_test.clj @@ -7,7 +7,6 @@ [metabase.sync.analyze.classify :as classify] [metabase.sync.interface :as i] [metabase.util :as u] - [metabase.util.date :as du] [toucan.util.test :as tt])) ;; Check that only the right Fields get classified @@ -23,7 +22,7 @@ Field [_ {:table_id (u/get-id table) :name "Current fingerprint, already analzed" :fingerprint_version Short/MAX_VALUE - :last_analyzed (du/->Timestamp #inst "2017-08-09")}] + :last_analyzed #t "2017-08-09"}] Field [_ {:table_id (u/get-id table) :name "Old fingerprint, not analyzed" :fingerprint_version (dec Short/MAX_VALUE) @@ -31,7 +30,7 @@ Field [_ {:table_id (u/get-id table) :name "Old fingerprint, already analzed" :fingerprint_version (dec Short/MAX_VALUE) - :last_analyzed (du/->Timestamp #inst "2017-08-09")}]] + :last_analyzed #t "2017-08-09"}]] (for [field (#'classify/fields-to-classify table)] (:name field))))) diff --git a/test/metabase/sync/analyze/fingerprint/fingerprinters_test.clj b/test/metabase/sync/analyze/fingerprint/fingerprinters_test.clj index abb0bf9e879..e317425b6d4 100644 --- a/test/metabase/sync/analyze/fingerprint/fingerprinters_test.clj +++ b/test/metabase/sync/analyze/fingerprint/fingerprinters_test.clj @@ -1,58 +1,56 @@ (ns metabase.sync.analyze.fingerprint.fingerprinters-test - (:require [expectations :refer :all] + (:require [clojure.test :refer :all] [metabase.models.field :as field] - [metabase.sync.analyze.fingerprint.fingerprinters :refer :all] - [metabase.util.date :as du])) + [metabase.sync.analyze.fingerprint.fingerprinters :refer :all])) -(expect - {:global {:distinct-count 3 - :nil% 0.0} - :type {:type/DateTime {:earliest (du/date->iso-8601 #inst "2013") - :latest (du/date->iso-8601 #inst "2018")}}} - (transduce identity - (fingerprinter (field/map->FieldInstance {:base_type :type/DateTime})) - [#inst "2013" #inst "2018" #inst "2015"])) +(deftest fingerprint-temporal-values-test + (is (= {:global {:distinct-count 4 + :nil% 0.5} + :type {:type/DateTime {:earliest "2013-01-01" + :latest "2018-01-01"}}} + (transduce identity + (fingerprinter (field/map->FieldInstance {:base_type :type/DateTime})) + [#t "2013" nil #t "2018" nil nil #t "2015"]))) + (testing "nil temporal values" + {:global {:distinct-count 1 + :nil% 1.0} + :type {:type/DateTime {:earliest nil + :latest nil}}} + (transduce identity + (fingerprinter (field/map->FieldInstance {:base_type :type/DateTime})) + (repeat 10 nil)))) -;; Correctly disambiguate multiple competing multimethods -(expect - {:global {:distinct-count 3 - :nil% 0.0} - :type {:type/DateTime {:earliest (du/date->iso-8601 #inst "2013") - :latest (du/date->iso-8601 #inst "2018")}}} - (transduce identity - (fingerprinter (field/map->FieldInstance {:base_type :type/DateTime - :special_type :type/FK})) - [#inst "2013" #inst "2018" #inst "2015"])) +(deftest disambiguate-test + (testing "We should correctly disambiguate multiple competing multimethods (DateTime and FK in this case)" + (is (= {:global {:distinct-count 3 + :nil% 0.0} + :type {:type/DateTime {:earliest "2013-01-01" + :latest "2018-01-01"}}} + (transduce identity + (fingerprinter (field/map->FieldInstance {:base_type :type/DateTime + :special_type :type/FK})) + [#t "2013" #t "2018" #t "2015"]))))) -(expect - {:global {:distinct-count 1 - :nil% 1.0} - :type {:type/DateTime {:earliest nil - :latest nil}}} - (transduce identity - (fingerprinter (field/map->FieldInstance {:base_type :type/DateTime})) - (repeat 10 nil))) +(deftest fingerprint-numeric-values-test + (is (= {:global {:distinct-count 3 + :nil% 0.0} + :type {:type/Number {:avg 2.0 + :min 1.0 + :max 3.0 + :q1 1.25 + :q3 2.75 + :sd 1.0}}} + (transduce identity + (fingerprinter (field/map->FieldInstance {:base_type :type/Number})) + [1.0 2.0 3.0])))) -(expect - {:global {:distinct-count 3 - :nil% 0.0} - :type {:type/Number {:avg 2.0 - :min 1.0 - :max 3.0 - :q1 1.25 - :q3 2.75 - :sd 1.0}}} - (transduce identity - (fingerprinter (field/map->FieldInstance {:base_type :type/Number})) - [1.0 2.0 3.0])) - -(expect - {:global {:distinct-count 5 - :nil% 0.0} - :type {:type/Text {:percent-json 0.2, - :percent-url 0.0, - :percent-email 0.0, - :average-length 6.4}}} - (transduce identity - (fingerprinter (field/map->FieldInstance {:base_type :type/Text})) - ["metabase" "more" "like" "metabae" "[1, 2, 3]"])) +(deftest fingerprint-string-values-test + (is (= {:global {:distinct-count 5 + :nil% 0.0} + :type {:type/Text {:percent-json 0.2 + :percent-url 0.0 + :percent-email 0.0 + :average-length 6.4}}} + (transduce identity + (fingerprinter (field/map->FieldInstance {:base_type :type/Text})) + ["metabase" "more" "like" "metabae" "[1, 2, 3]"])))) diff --git a/test/metabase/sync/analyze/fingerprint/insights_test.clj b/test/metabase/sync/analyze/fingerprint/insights_test.clj index c2be0ca1027..39864469411 100644 --- a/test/metabase/sync/analyze/fingerprint/insights_test.clj +++ b/test/metabase/sync/analyze/fingerprint/insights_test.clj @@ -1,38 +1,29 @@ (ns metabase.sync.analyze.fingerprint.insights-test - (:require [expectations :refer :all] + (:require [clojure.test :refer :all] + [expectations :refer :all] [metabase.sync.analyze.fingerprint.insights :as i :refer :all])) (def ^:private cols [{:base_type :type/DateTime} {:base_type :type/Number}]) -(expect - 700 - (-> (transduce identity (insights cols) [["2014" 100] - ["2015" 200] - ["2016" nil] - [nil 300] - [nil nil] - ["2017" 700]]) - first - :last-value)) - -(expect - 700 - (-> (transduce identity (insights cols) [["2017" 700]]) - first - :last-value)) - -;; Here we just make sure we don't blow up on empty input -(expect - nil - (-> (transduce identity (insights cols) []) - first - :last-value)) - -(expect - nil - (-> (transduce identity (insights cols) [[nil nil]]) - first - :last-value)) +(deftest last-value-test + (doseq [{:keys [rows expected]} [{:rows [["2014" 100] + ["2015" 200] + ["2016" nil] + [nil 300] + [nil nil] + ["2017" 700]] + :expected 700} + {:rows [["2017" 700]] + :expected 700} + {:rows [] + :expected nil} + {:rows [[nil nil]] + :expected nil}]] + (testing (format "rows = %s" rows) + (is (= expected + (-> (transduce identity (insights cols) rows) + first + :last-value)))))) (expect (transduce identity @@ -42,50 +33,39 @@ ["2015-01-01T00:00:00Z" 200]])) (defn- inst->day - [inst] - (some-> inst (.getTime) (#'i/ms->day))) + [t] + (some-> t (#'i/->millis-from-epoch) (#'i/ms->day))) (defn- valid-period? ([from to] (valid-period? from to (#'i/infer-unit (inst->day from) (inst->day to)))) ([from to period] (boolean (#'i/valid-period? (inst->day from) (inst->day to) period)))) -(expect - true - (valid-period? #inst "2015-01" #inst "2015-02")) -(expect - true - (valid-period? #inst "2015-02" #inst "2015-03")) -(expect - false - (valid-period? #inst "2015-01" #inst "2015-03")) -(expect - false - (valid-period? #inst "2015-01" nil)) -(expect - true - (valid-period? #inst "2015-01-01" #inst "2015-01-02")) -(expect - true - (valid-period? #inst "2015-01-01" #inst "2015-01-08")) -(expect - true - (valid-period? #inst "2015-01-01" #inst "2015-04-03")) -(expect - true - (valid-period? #inst "2015" #inst "2016")) -(expect - false - (valid-period? #inst "2015-01-01" #inst "2015-01-09")) -(expect - true - (valid-period? #inst "2015-01-01" #inst "2015-04-03" :quarter)) -(expect - false - (valid-period? #inst "2015-01-01" #inst "2015-04-03" :month)) -(expect - false - (valid-period? #inst "2015-01" #inst "2015-02" nil)) +(deftest valid-period-test + (is (= true + (valid-period? #t "2015-01" #t "2015-02"))) + (is (= true + (valid-period? #t "2015-02" #t "2015-03"))) + (is (= false + (valid-period? #t "2015-01" #t "2015-03"))) + (is (= false + (valid-period? #t "2015-01" nil))) + (is (= true + (valid-period? #t "2015-01-01" #t "2015-01-02"))) + (is (= true + (valid-period? #t "2015-01-01" #t "2015-01-08"))) + (is (= true + (valid-period? #t "2015-01-01" #t "2015-04-03"))) + (is (= true + (valid-period? #t "2015" #t "2016"))) + (is (= false + (valid-period? #t "2015-01-01" #t "2015-01-09"))) + (is (= true + (valid-period? #t "2015-01-01" #t "2015-04-03" :quarter))) + (is (= false + (valid-period? #t "2015-01-01" #t "2015-04-03" :month))) + (is (= false + (valid-period? #t "2015-01" #t "2015-02" nil)))) ;; Make sure we don't return nosense results like infinitiy coeficients diff --git a/test/metabase/sync/analyze/fingerprint_test.clj b/test/metabase/sync/analyze/fingerprint_test.clj index fe9222f273b..a6f27c01cd8 100644 --- a/test/metabase/sync/analyze/fingerprint_test.clj +++ b/test/metabase/sync/analyze/fingerprint_test.clj @@ -12,7 +12,6 @@ [metabase.sync.analyze.fingerprint.fingerprinters :as fingerprinters] [metabase.sync.interface :as i] [metabase.test.data :as data] - [metabase.util.date :as du] [toucan.db :as db] [toucan.util.test :as tt])) @@ -219,7 +218,7 @@ :table_id (data/id :venues) :fingerprint nil :fingerprint_version 1 - :last_analyzed (du/->Timestamp #inst "2017-08-09")}] + :last_analyzed #t "2017-08-09T00:00:00"}] (with-redefs [i/latest-fingerprint-version 3 metadata-queries/table-rows-sample (constantly [[1] [2] [3] [4] [5]]) fingerprinters/fingerprinter (constantly (fingerprinters/constant-fingerprinter {:experimental {:fake-fingerprint? true}}))] diff --git a/test/metabase/sync/analyze_test.clj b/test/metabase/sync/analyze_test.clj index 47e9db899dc..878063a0a70 100644 --- a/test/metabase/sync/analyze_test.clj +++ b/test/metabase/sync/analyze_test.clj @@ -1,5 +1,6 @@ (ns metabase.sync.analyze-test - (:require [expectations :refer :all] + (:require [clojure.test :refer :all] + [expectations :refer [expect]] [metabase.models [database :refer [Database]] [field :refer [Field]] @@ -10,35 +11,27 @@ [sync-metadata :as sync-metadata]] [metabase.test.data :as data] [metabase.util :as u] - [metabase.util.date :as du] [toucan.db :as db] [toucan.util.test :as tt])) -(def ^:private fake-analysis-completion-date - (du/->Timestamp #inst "2017-08-01")) - -;; Check that Fields do *not* get analyzed if they're not newly created and fingerprint version is current -(expect - ;; PK is ok because it gets marked as part of metadata sync - #{{:name "LONGITUDE", :special_type nil, :last_analyzed fake-analysis-completion-date} - {:name "CATEGORY_ID", :special_type nil, :last_analyzed fake-analysis-completion-date} - {:name "PRICE", :special_type nil, :last_analyzed fake-analysis-completion-date} - {:name "LATITUDE", :special_type nil, :last_analyzed fake-analysis-completion-date} - {:name "NAME", :special_type nil, :last_analyzed fake-analysis-completion-date} - {:name "ID", :special_type :type/PK, :last_analyzed fake-analysis-completion-date}} - (tt/with-temp* [Database [db {:engine "h2", :details (:details (data/db))}] - Table [table {:name "VENUES", :db_id (u/get-id db)}]] - ;; sync the metadata, but DON't do analysis YET - (sync-metadata/sync-table-metadata! table) - ;; now mark all the Tables as analyzed with so they won't be subject to analysis - (db/update-where! Field {:table_id (u/get-id table)} - :last_analyzed fake-analysis-completion-date - :fingerprint_version Short/MAX_VALUE) - ;; ok, NOW run the analysis process - (analyze/analyze-table! table) - ;; check and make sure all the Fields don't have special types and their last_analyzed date didn't change - (set (for [field (db/select [Field :name :special_type :last_analyzed] :table_id (u/get-id table))] - (into {} field))))) +(deftest skip-analysis-of-fields-with-current-fingerprint-version-test + (testing "Check that Fields do *not* get analyzed if they're not newly created and fingerprint version is current" + (data/with-temp-copy-of-db + ;; mark all the Fields as analyzed with so they won't be subject to analysis + (db/update-where! Field {:table_id (data/id :venues)} + :last_analyzed #t "2017-08-01T00:00" + :special_type nil + :fingerprint_version Short/MAX_VALUE) + ;; the type of the value that comes back may differ a bit between different application DBs + (let [analysis-date (db/select-one-field :last_analyzed Field :table_id (data/id :venues))] + ;; ok, NOW run the analysis process + (analyze/analyze-table! (Table (data/id :venues))) + ;; check and make sure all the Fields don't have special types and their last_analyzed date didn't change + ;; PK is ok because it gets marked as part of metadata sync + (is (= (zipmap ["CATEGORY_ID" "ID" "LATITUDE" "LONGITUDE" "NAME" "PRICE"] + (repeat {:special_type nil, :last_analyzed analysis-date})) + (into {} (for [field (db/select [Field :name :special_type :last_analyzed] :table_id (data/id :venues))] + [(:name field) (into {} (dissoc field :name))])))))))) ;; ...but they *SHOULD* get analyzed if they ARE newly created (expcept for PK which we skip) (expect @@ -58,27 +51,26 @@ (set (for [field (db/select [Field :name :special_type :last_analyzed] :table_id (u/get-id table))] (into {} (update field :last_analyzed boolean)))))) -;; Make sure that only the correct Fields get marked as recently analyzed -(expect - #{"Current fingerprint, not analyzed"} - (with-redefs [i/latest-fingerprint-version Short/MAX_VALUE - du/new-sql-timestamp (constantly (du/->Timestamp #inst "1999-01-01"))] - (tt/with-temp* [Table [table] - Field [_ {:table_id (u/get-id table) - :name "Current fingerprint, not analyzed" - :fingerprint_version Short/MAX_VALUE - :last_analyzed nil}] - Field [_ {:table_id (u/get-id table) - :name "Current fingerprint, already analzed" - :fingerprint_version Short/MAX_VALUE - :last_analyzed (du/->Timestamp #inst "2017-08-09")}] - Field [_ {:table_id (u/get-id table) - :name "Old fingerprint, not analyzed" - :fingerprint_version (dec Short/MAX_VALUE) - :last_analyzed nil}] - Field [_ {:table_id (u/get-id table) - :name "Old fingerprint, already analzed" - :fingerprint_version (dec Short/MAX_VALUE) - :last_analyzed (du/->Timestamp #inst "2017-08-09")}]] - (#'analyze/update-fields-last-analyzed! table) - (db/select-field :name Field :last_analyzed (du/new-sql-timestamp))))) +(deftest mark-fields-as-analyzed-test + (testing "Make sure that only the correct Fields get marked as recently analyzed" + (with-redefs [i/latest-fingerprint-version Short/MAX_VALUE] + (tt/with-temp* [Table [table] + Field [_ {:table_id (u/get-id table) + :name "Current fingerprint, not analyzed" + :fingerprint_version Short/MAX_VALUE + :last_analyzed nil}] + Field [_ {:table_id (u/get-id table) + :name "Current fingerprint, already analzed" + :fingerprint_version Short/MAX_VALUE + :last_analyzed #t "2017-08-09T00:00Z"}] + Field [_ {:table_id (u/get-id table) + :name "Old fingerprint, not analyzed" + :fingerprint_version (dec Short/MAX_VALUE) + :last_analyzed nil}] + Field [_ {:table_id (u/get-id table) + :name "Old fingerprint, already analzed" + :fingerprint_version (dec Short/MAX_VALUE) + :last_analyzed #t "2017-08-09T00:00Z"}]] + (#'analyze/update-fields-last-analyzed! table) + (is (= #{"Current fingerprint, not analyzed"} + (db/select-field :name Field :table_id (u/get-id table), :last_analyzed [:> #t "2018-01-01"]))))))) diff --git a/test/metabase/sync/util_test.clj b/test/metabase/sync/util_test.clj index ba9c30300bb..4fe8081b384 100644 --- a/test/metabase/sync/util_test.clj +++ b/test/metabase/sync/util_test.clj @@ -1,8 +1,10 @@ (ns metabase.sync.util-test "Tests for the utility functions shared by all parts of sync, such as the duplicate ops guard." - (:require [clj-time.core :as time] - [clojure.string :as str] - [expectations :refer :all] + (:require [clojure + [string :as str] + [test :refer :all]] + [expectations :refer [expect]] + [java-time :as t] [metabase [driver :as driver] [sync :as sync]] @@ -11,7 +13,6 @@ [task-history :refer [TaskHistory]]] [metabase.sync.util :as sync-util :refer :all] [metabase.test.util :as tu] - [metabase.util.date :as du] [toucan.db :as db] [toucan.util.test :as tt])) @@ -32,28 +33,28 @@ (defmethod driver/describe-table ::concurrent-sync-test [& _] nil) -;; only one sync should be going on at a time -(expect - ;; describe-database gets called twice during a single sync process, once for syncing tables and a second time for - ;; syncing the _metabase_metadata table - 2 - (tt/with-temp* [Database [db {:engine ::concurrent-sync-test}]] - (reset! calls-to-describe-database 0) - ;; start a sync processes in the background. It should take 1000 ms to finish - (let [f1 (future (sync/sync-database! db)) - f2 (do - ;; wait 200 ms to make sure everything is going - (Thread/sleep 200) - ;; Start another in the background. Nothing should happen here because the first is already running - (future (sync/sync-database! db)))] - ;; Start another in the foreground. Again, nothing should happen here because the original should still be - ;; running - (sync/sync-database! db) - ;; make sure both of the futures have finished - (deref f1) - (deref f2) - ;; Check the number of syncs that took place. Should be 2 (just the first) - @calls-to-describe-database))) +(deftest concurrent-sync-test + (testing "only one sync process be going on at a time" + ;; describe-database gets called twice during a single sync process, once for syncing tables and a second time for + ;; syncing the _metabase_metadata table + (tt/with-temp* [Database [db {:engine ::concurrent-sync-test}]] + (reset! calls-to-describe-database 0) + ;; start a sync processes in the background. It should take 1000 ms to finish + (let [f1 (future (sync/sync-database! db)) + f2 (do + ;; wait 200 ms to make sure everything is going + (Thread/sleep 200) + ;; Start another in the background. Nothing should happen here because the first is already running + (future (sync/sync-database! db)))] + ;; Start another in the foreground. Again, nothing should happen here because the original should still be + ;; running + (sync/sync-database! db) + ;; make sure both of the futures have finished + (deref f1) + (deref f2) + ;; Check the number of syncs that took place. Should be 2 (just the first) + (is (= 2 + @calls-to-describe-database)))))) (defn- call-with-operation-info "Call `f` with `log-sync-summary` and `store-sync-summary!` redef'd. For `log-sync-summary`, it intercepts the step @@ -61,20 +62,20 @@ TaskHistory rows. This is useful to validate that the metadata and history is correct as the message might not be logged at all (depending on the logging level) or not stored." [f] - (let [step-info-atom (atom []) + (let [step-info-atom (atom []) created-task-history-ids (atom []) - orig-log-fn (var-get #'metabase.sync.util/log-sync-summary) - orig-store-fn (var-get #'metabase.sync.util/store-sync-summary!)] - (with-redefs [metabase.sync.util/log-sync-summary (fn [operation database {:keys [steps] :as operation-metadata}] - (swap! step-info-atom conj operation-metadata) - (orig-log-fn operation database operation-metadata)) + orig-log-fn @#'metabase.sync.util/log-sync-summary + orig-store-fn @#'metabase.sync.util/store-sync-summary!] + (with-redefs [metabase.sync.util/log-sync-summary (fn [operation database {:keys [steps] :as operation-metadata}] + (swap! step-info-atom conj operation-metadata) + (orig-log-fn operation database operation-metadata)) metabase.sync.util/store-sync-summary! (fn [operation database operation-metadata] (let [result (orig-store-fn operation database operation-metadata)] (swap! created-task-history-ids concat result) result))] (f)) {:operation-results @step-info-atom - :task-history-ids @created-task-history-ids})) + :task-history-ids @created-task-history-ids})) (defn sync-database! "Calls `sync-database!` and returns the the metadata for `step` as the result along with the `TaskHistory` for that @@ -94,9 +95,8 @@ [step-info] (dissoc step-info :start-time :end-time :log-summary-fn)) -(defn- validate-times [m] - (and (-> m :start-time du/is-temporal?) - (-> m :end-time du/is-temporal?))) +(defn- validate-times [{:keys [start-time end-time]}] + (every? (partial instance? java.time.temporal.Temporal) [start-time end-time])) (def ^:private default-task-history {:id true, :db_id true, :started_at true, :ended_at true}) @@ -134,54 +134,60 @@ :step-2-history (fetch-task-history-row step-2-name)}))) (defn- create-test-sync-summary [step-name log-summary-fn] - (let [start (time/now)] + (let [start (t/zoned-date-time)] {:start-time start - :end-time (time/plus start (time/seconds 5)) - :steps [[step-name {:start-time start - :end-time (time/plus start (time/seconds 4)) - :log-summary-fn log-summary-fn}]]})) - -;; Test that we can create the log summary message. This is a big string blob, so validate that it contains the -;; important parts and it doesn't throw an exception -(expect - {:has-operation? true - :has-db-name? true - :has-operation-duration? true - :has-step-name? true - :has-step-duration? true - :has-log-summary-text? true} - (let [operation (tu/random-name) - db-name (tu/random-name) - step-name (tu/random-name) - step-log-text (tu/random-name) - results (#'sync-util/make-log-sync-summary-str operation - (mdb/map->DatabaseInstance {:name db-name}) - (create-test-sync-summary step-name - (fn [step-info] - step-log-text)))] - {:has-operation? (str/includes? results operation) - :has-db-name? (str/includes? results db-name) - :has-operation-duration? (str/includes? results "5.0 s") - :has-step-name? (str/includes? results step-name) - :has-step-duration? (str/includes? results "4.0 s") - :has-log-summary-text? (str/includes? results step-log-text)})) - -;; The `log-summary-fn` part of step info is optional as not all steps have it. Validate that we properly handle that -;; case -(expect - {:has-operation? true - :has-db-name? true - :has-operation-duration? true - :has-step-name? true - :has-step-duration? true} + :end-time (t/plus start (t/seconds 5)) + :steps [[step-name {:start-time start + :end-time (t/plus start (t/seconds 4)) + :log-summary-fn log-summary-fn}]]})) + +(deftest log-summary-message-test (let [operation (tu/random-name) db-name (tu/random-name) - step-name (tu/random-name) - results (#'sync-util/make-log-sync-summary-str operation - (mdb/map->DatabaseInstance {:name db-name}) - (create-test-sync-summary step-name nil))] - {:has-operation? (str/includes? results operation) - :has-db-name? (str/includes? results db-name) - :has-operation-duration? (str/includes? results "5.0 s") - :has-step-name? (str/includes? results step-name) - :has-step-duration? (str/includes? results "4.0 s")})) + step-name (tu/random-name)] + (testing (str "Test that we can create the log summary message. This is a big string blob, so validate that it" + " contains the important parts and it doesn't throw an exception") + (let [step-log-text (tu/random-name) + results (#'sync-util/make-log-sync-summary-str operation + (mdb/map->DatabaseInstance {:name db-name}) + (create-test-sync-summary step-name + (fn [step-info] + step-log-text)))] + (testing "has-operation?" + (is (= true + (str/includes? results operation)))) + (testing "has-db-name?" + (is (= true + (str/includes? results db-name)))) + (testing "has-operation-duration?" + (is (= true + (str/includes? results "5.0 s")))) + (testing "has-step-name?" + (is (= true + (str/includes? results step-name)))) + (testing "has-step-duration?" + (is (= true + (str/includes? results "4.0 s")))) + (testing "has-log-summary-text?" + (is (= true + (str/includes? results step-log-text)))))) + (testing (str "The `log-summary-fn` part of step info is optional as not all steps have it. Validate that we" + " properly handle that case") + (let [results (#'sync-util/make-log-sync-summary-str operation + (mdb/map->DatabaseInstance {:name db-name}) + (create-test-sync-summary step-name nil))] + (testing "has-operation?" + (is (= true + (str/includes? results operation)))) + (testing "has-db-name?" + (is (= true + (str/includes? results db-name)))) + (testing "has-operation-duration?" + (is (= true + (str/includes? results "5.0 s")))) + (testing "has-step-name?" + (is (= true + (str/includes? results step-name)))) + (testing "has-step-duration?" + (is (= true + (str/includes? results "4.0 s")))))))) diff --git a/test/metabase/sync_database/sync_dynamic_test.clj b/test/metabase/sync_database/sync_dynamic_test.clj index 2f285494aae..93ea838cd36 100644 --- a/test/metabase/sync_database/sync_dynamic_test.clj +++ b/test/metabase/sync_database/sync_dynamic_test.clj @@ -18,8 +18,8 @@ [toucan.util.test :as tt])) (defn- remove-nonsense - "Remove fields that aren't really relevant in the output for TABLES and their FIELDS. - Done for the sake of making debugging some of the tests below easier." + "Remove fields that aren't really relevant in the output for `tables` and their `fields`. Done for the sake of making + debugging some of the tests below easier." [tables] (for [table tables] (-> (u/select-non-nil-keys table [:schema :name :fields]) diff --git a/test/metabase/task/follow_up_emails_test.clj b/test/metabase/task/follow_up_emails_test.clj index aafe5dc43d3..29eaaa644cd 100644 --- a/test/metabase/task/follow_up_emails_test.clj +++ b/test/metabase/task/follow_up_emails_test.clj @@ -1,6 +1,6 @@ (ns metabase.task.follow-up-emails-test (:require [clojure.test :refer :all] - [expectations :refer [expect]] + [java-time :as t] [metabase.email-test :refer [inbox with-fake-inbox]] [metabase.task.follow-up-emails :as follow-up-emails] [metabase.test @@ -9,13 +9,30 @@ (use-fixtures :once (fixtures/initialize :test-users)) -;; Make sure that `send-follow-up-email!` only sends a single email instead even when triggered multiple times (#4253) -;; follow-up emails get sent to the oldest admin -(expect - 1 +(deftest send-follow-up-email-test + (testing (str "Make sure that `send-follow-up-email!` only sends a single email instead even when triggered multiple " + "times (#4253) follow-up emails get sent to the oldest admin")) (tu/with-temporary-setting-values [anon-tracking-enabled true follow-up-email-sent false] (with-fake-inbox (#'follow-up-emails/send-follow-up-email!) (#'follow-up-emails/send-follow-up-email!) - (-> @inbox vals first count)))) + (is (= 1 + (-> @inbox vals first count)))))) + +(deftest should-send-abandoment-email-test + (testing "Conditions where abandonment emails should be sent" + (doseq [now [(t/zoned-date-time) (t/offset-date-time) (t/instant)] + instance-creation [0 1 5 nil] + last-user [0 1 3 nil] + last-activity [0 1 3 nil] + last-view [0 1 3 nil]] + (testing (format "classes = %s, instance creation = %d weeks ago, last-user = %d weeks ago, last-activity = %d weeks ago, last-view = %d weeks ago" + (.getName (class now)) instance-creation last-user last-activity last-view) + (is (= (and (= instance-creation 5) + (every? #(contains? #{nil 3} %) [last-user last-activity last-view])) + (#'follow-up-emails/should-send-abandoment-email? + (when instance-creation (t/minus now (t/weeks instance-creation))) + (when last-user (t/minus now (t/weeks last-user))) + (when last-activity (t/minus now (t/weeks last-activity))) + (when last-view (t/minus now (t/weeks last-view)))))))))) diff --git a/test/metabase/task/sync_databases_test.clj b/test/metabase/task/sync_databases_test.clj index 8d942efe2cd..e539c5239a5 100644 --- a/test/metabase/task/sync_databases_test.clj +++ b/test/metabase/task/sync_databases_test.clj @@ -9,7 +9,7 @@ [metabase.test.util :as tu] [metabase.test.util.log :as tu.log] [metabase.util :as u] - [metabase.util.date :as du] + [metabase.util.date-2 :as u.date] [toucan.db :as db] [toucan.util.test :as tt]) (:import [metabase.task.sync_databases SyncAndAnalyzeDatabase UpdateFieldValues])) @@ -168,7 +168,7 @@ :ran-update-field-values? (deref update-field-values-ran? 500 false)}))))) (defn- cron-schedule-for-next-year [] - (format "0 15 10 * * ? %d" (inc (du/date-extract :year)))) + (format "0 15 10 * * ? %d" (inc (u.date/extract :year)))) ;; Make sure that a database that *is* marked full sync *will* get analyzed (expect diff --git a/test/metabase/task/task_history_cleanup_test.clj b/test/metabase/task/task_history_cleanup_test.clj index 53f0868b1cc..b46f7a46b77 100644 --- a/test/metabase/task/task_history_cleanup_test.clj +++ b/test/metabase/task/task_history_cleanup_test.clj @@ -1,6 +1,6 @@ (ns metabase.task.task-history-cleanup-test - (:require [clj-time.core :as time] - [expectations :refer :all] + (:require [clojure.test :refer :all] + [java-time :as t] [metabase.models [task-history :refer [TaskHistory]] [task-history-test :as tht]] @@ -10,42 +10,35 @@ [toucan.db :as db] [toucan.util.test :as tt])) -;; Basic run of the cleanup task when it needs to remove rows. Should also add a TaskHistory row once complete -(let [task-2 (tu/random-name) - task-3 (tu/random-name)] - (expect - #{task-2 task-3 "task-history-cleanup"} - (let [t1-start (time/now) - t2-start (tht/add-second t1-start) - t3-start (tht/add-second t2-start)] - (tt/with-temp* [TaskHistory [t1 (tht/make-10-millis-task t1-start)] - TaskHistory [t2 (assoc (tht/make-10-millis-task t2-start) - :task task-2)] - TaskHistory [t3 (assoc (tht/make-10-millis-task t3-start) - :task task-3)]] - (with-redefs [cleanup-task/history-rows-to-keep 2] - (db/delete! TaskHistory :id [:not-in (map u/get-id [t1 t2 t3])]) - ;; Delete all but 2 task history rows - (#'cleanup-task/task-history-cleanup!) - (set (map :task (TaskHistory)))))))) - -;; When the task runs and nothing is removed, it should still insert a new TaskHistory row -(let [task-1 (tu/random-name) - task-2 (tu/random-name) - task-3 (tu/random-name)] - (expect - #{task-1 task-2 task-3 "task-history-cleanup"} - (let [t1-start (time/now) - t2-start (tht/add-second t1-start) - t3-start (tht/add-second t2-start)] - (tt/with-temp* [TaskHistory [t1 (assoc (tht/make-10-millis-task t1-start) - :task task-1)] - TaskHistory [t2 (assoc (tht/make-10-millis-task t2-start) - :task task-2)] - TaskHistory [t3 (assoc (tht/make-10-millis-task t3-start) - :task task-3)]] - (with-redefs [cleanup-task/history-rows-to-keep 10] - (db/delete! TaskHistory :id [:not-in (map u/get-id [t1 t2 t3])]) - ;; Delete all but 2 task history rows - (#'cleanup-task/task-history-cleanup!) - (set (map :task (TaskHistory)))))))) +(deftest cleanup-test + (let [task-1 (tu/random-name) + task-2 (tu/random-name) + task-3 (tu/random-name) + t1-start (t/offset-date-time) + t2-start (tht/add-second t1-start) + t3-start (tht/add-second t2-start)] + (letfn [(do-with-tasks [{:keys [rows-to-keep]} thunk] + (tt/with-temp* [TaskHistory [t1 (assoc (tht/make-10-millis-task t1-start) + :task task-1)] + TaskHistory [t2 (assoc (tht/make-10-millis-task t2-start) + :task task-2)] + TaskHistory [t3 (assoc (tht/make-10-millis-task t3-start) + :task task-3)]] + (db/delete! TaskHistory :id [:not-in (map u/get-id [t1 t2 t3])]) + (with-redefs [cleanup-task/history-rows-to-keep rows-to-keep] + (#'cleanup-task/task-history-cleanup!)) + (thunk))) + (task-history-tasks [] + (set (map :task (TaskHistory))))] + (testing "Basic run of the cleanup task when it needs to remove rows. Should also add a TaskHistory row once complete" + (do-with-tasks + {:rows-to-keep 2} + (fn [] + (is (= #{task-2 task-3 "task-history-cleanup"} + (task-history-tasks)))))) + (testing "When the task runs and nothing is removed, it should still insert a new TaskHistory row" + (do-with-tasks + {:rows-to-keep 10} + (fn [] + (is (= #{task-1 task-2 task-3 "task-history-cleanup"} + (task-history-tasks))))))))) diff --git a/test/metabase/test/data/dataset_definitions.clj b/test/metabase/test/data/dataset_definitions.clj index 9a6ef4dcf68..6a205c63a1a 100644 --- a/test/metabase/test/data/dataset_definitions.clj +++ b/test/metabase/test/data/dataset_definitions.clj @@ -2,9 +2,10 @@ "Definitions of various datasets for use in tests with `data/dataset` and the like." (:require [java-time :as t] [medley.core :as m] - [metabase.test.data.interface :as tx]) + [metabase.test.data.interface :as tx] + [metabase.util.date-2 :as u.date]) (:import java.sql.Time - [java.util Calendar TimeZone])) + [java.time LocalDate LocalDateTime LocalTime OffsetDateTime OffsetTime ZonedDateTime])) ;;; +----------------------------------------------------------------------------------------------------------------+ ;;; | Various Datasets | @@ -50,28 +51,30 @@ not explicitly marked as a foreign key, because the test dataset syntax does not yet have a way to support nullable foreign keys.)") -;; TIMEZONE FIXME - all of this stuff should be updated to use `java.time` instead -(defn- calendar-with-fields ^Calendar [date & fields] - (let [^Calendar cal-from-date (doto (Calendar/getInstance (TimeZone/getTimeZone "UTC")) - (.setTime date)) - ^Calendar blank-calendar (doto ^Calendar (.clone cal-from-date) - .clear)] - (doseq [field fields] - (.set blank-calendar field (.get cal-from-date field))) - blank-calendar)) - (defn- date-only - "This function emulates a date only field as it would come from the JDBC driver. The hour/minute/second/millisecond - fields should be 0s" - [date] - (.getTime (calendar-with-fields date Calendar/DAY_OF_MONTH Calendar/MONTH Calendar/YEAR))) + "Convert date or datetime temporal value to `t` to an appropriate date type, discarding time information." + [t] + (when t + (condp instance? t + LocalDate t + LocalDateTime (t/local-date t) + LocalTime (throw (Exception. "Cannot convert a time to a date")) + OffsetTime (throw (Exception. "Cannot convert a time to a date")) + ;; since there is no `OffsetDate` class use `OffsetDateTime`, but truncated to day + OffsetDateTime (u.date/truncate :day) + ZonedDateTime (u.date/truncate :day)))) (defn- time-only - "This function will return a java.sql.Time object. To create a Time object similar to what JDBC would return, the time - needs to be relative to epoch. As an example a time of 4:30 would be a Time instance, but it's a subclass of Date, - so it looks like 1970-01-01T04:30:00.000" - [date] - (Time. (.getTimeInMillis (calendar-with-fields date Calendar/HOUR_OF_DAY Calendar/MINUTE Calendar/SECOND)))) + "Convert time or datetime temporal value to `t` to an appropriate time type, discarding date information." + [t] + (when t + (condp instance? t + LocalDate (throw (Exception. "Cannot convert a date to a time")) + LocalDateTime (t/local-time t) + LocalTime t + OffsetTime t + OffsetDateTime (t/offset-time t) + ZonedDateTime (t/offset-time t)))) (defonce ^{:doc "The main `test-data` dataset, but only the `users` table, and with `last_login_date` and @@ -147,36 +150,35 @@ "A dataset for testing temporal values with and without timezones. Records of number of crow counts spoted and the date/time when they spotting occured in several different column types." [["attempts" - [{:field-name "num-crows", :base-type :type/Integer} - {:field-name "date", :base-type :type/Date} - {:field-name "time", :base-type :type/Time} - {:field-name "time-ltz", :base-type :type/TimeWithLocalTZ} - {:field-name "time-tz", :base-type :type/TimeWithZoneOffset} + [{:field-name "num-crows", :base-type :type/Integer} + {:field-name "date", :base-type :type/Date} + {:field-name "time", :base-type :type/Time} + {:field-name "time-ltz", :base-type :type/TimeWithLocalTZ} + {:field-name "time-tz", :base-type :type/TimeWithZoneOffset} {:field-name "datetime", :base-type :type/DateTime} {:field-name "datetime-ltz", :base-type :type/DateTimeWithLocalTZ} {:field-name "datetime-tz", :base-type :type/DateTimeWithZoneOffset} {:field-name "datetime-tz-id", :base-type :type/DateTimeWithZoneID}] - (for [[cnt s] [[6 "2019-11-01T00:23:18.331-07:00[America/Los_Angeles]"] - [8 "2019-11-02T00:14:14.246-07:00[America/Los_Angeles]"] - [6 "2019-11-03T23:35:17.906-08:00[America/Los_Angeles]"] - [7 "2019-11-04T01:04:09.593-08:00[America/Los_Angeles]"] - [8 "2019-11-05T14:23:46.411-08:00[America/Los_Angeles]"] - [4 "2019-11-06T18:51:16.270-08:00[America/Los_Angeles]"] - [6 "2019-11-07T02:45:34.443-08:00[America/Los_Angeles]"] - [4 "2019-11-08T19:51:39.753-08:00[America/Los_Angeles]"] - [3 "2019-11-09T09:59:10.483-08:00[America/Los_Angeles]"] - [1 "2019-11-10T08:41:35.860-08:00[America/Los_Angeles]"] - [5 "2019-11-11T08:09:08.892-08:00[America/Los_Angeles]"] - [3 "2019-11-12T07:36:16.088-08:00[America/Los_Angeles]"] - [2 "2019-11-13T04:28:40.489-08:00[America/Los_Angeles]"] - [9 "2019-11-14T09:52:17.242-08:00[America/Los_Angeles]"] - [7 "2019-11-15T16:07:25.292-08:00[America/Los_Angeles]"] - [7 "2019-11-16T13:32:16.936-08:00[America/Los_Angeles]"] - [1 "2019-11-17T14:11:38.076-08:00[America/Los_Angeles]"] - [3 "2019-11-18T20:47:27.902-08:00[America/Los_Angeles]"] - [5 "2019-11-19T00:35:23.146-08:00[America/Los_Angeles]"] - [1 "2019-11-20T20:09:55.752-08:00[America/Los_Angeles]"]] - :let [t (t/zoned-date-time s)]] + (for [[cnt t] [[6 #t "2019-11-01T00:23:18.331-07:00[America/Los_Angeles]"] + [8 #t "2019-11-02T00:14:14.246-07:00[America/Los_Angeles]"] + [6 #t "2019-11-03T23:35:17.906-08:00[America/Los_Angeles]"] + [7 #t "2019-11-04T01:04:09.593-08:00[America/Los_Angeles]"] + [8 #t "2019-11-05T14:23:46.411-08:00[America/Los_Angeles]"] + [4 #t "2019-11-06T18:51:16.270-08:00[America/Los_Angeles]"] + [6 #t "2019-11-07T02:45:34.443-08:00[America/Los_Angeles]"] + [4 #t "2019-11-08T19:51:39.753-08:00[America/Los_Angeles]"] + [3 #t "2019-11-09T09:59:10.483-08:00[America/Los_Angeles]"] + [1 #t "2019-11-10T08:41:35.860-08:00[America/Los_Angeles]"] + [5 #t "2019-11-11T08:09:08.892-08:00[America/Los_Angeles]"] + [3 #t "2019-11-12T07:36:16.088-08:00[America/Los_Angeles]"] + [2 #t "2019-11-13T04:28:40.489-08:00[America/Los_Angeles]"] + [9 #t "2019-11-14T09:52:17.242-08:00[America/Los_Angeles]"] + [7 #t "2019-11-15T16:07:25.292-08:00[America/Los_Angeles]"] + [7 #t "2019-11-16T13:32:16.936-08:00[America/Los_Angeles]"] + [1 #t "2019-11-17T14:11:38.076-08:00[America/Los_Angeles]"] + [3 #t "2019-11-18T20:47:27.902-08:00[America/Los_Angeles]"] + [5 #t "2019-11-19T00:35:23.146-08:00[America/Los_Angeles]"] + [1 #t "2019-11-20T20:09:55.752-08:00[America/Los_Angeles]"]]] [cnt ; num-crows (t/local-date t) ; date (t/local-time t) ; time diff --git a/test/metabase/test/data/dataset_definitions/daily-bird-counts.edn b/test/metabase/test/data/dataset_definitions/daily-bird-counts.edn index 658a7c639af..1d7d6b54f68 100644 --- a/test/metabase/test/data/dataset_definitions/daily-bird-counts.edn +++ b/test/metabase/test/data/dataset_definitions/daily-bird-counts.edn @@ -2,33 +2,33 @@ :base-type :type/Date} {:field-name "count" :base-type :type/Integer}] - [[#inst "2018-09-20" nil] - [#inst "2018-09-21" 0] - [#inst "2018-09-22" 0] - [#inst "2018-09-23" 10] - [#inst "2018-09-24" 8] - [#inst "2018-09-25" 5] - [#inst "2018-09-26" 5] - [#inst "2018-09-27" nil] - [#inst "2018-09-28" 0] - [#inst "2018-09-29" 0] - [#inst "2018-09-30" 11] - [#inst "2018-10-01" 14] - [#inst "2018-10-02" 8] - [#inst "2018-10-03" 14] - [#inst "2018-10-04" nil] - [#inst "2018-10-05" 6] - [#inst "2018-10-06" 4] - [#inst "2018-10-07" 0] - [#inst "2018-10-08" nil] - [#inst "2018-10-09" 3] - [#inst "2018-10-10" 13] - [#inst "2018-10-11" nil] - [#inst "2018-10-12" 14] - [#inst "2018-10-13" 6] - [#inst "2018-10-14" 12] - [#inst "2018-10-15" 13] - [#inst "2018-10-16" 0] - [#inst "2018-10-17" 7] - [#inst "2018-10-18" 10] - [#inst "2018-10-19" 5]]]] + [[#t "2018-09-20" nil] + [#t "2018-09-21" 0] + [#t "2018-09-22" 0] + [#t "2018-09-23" 10] + [#t "2018-09-24" 8] + [#t "2018-09-25" 5] + [#t "2018-09-26" 5] + [#t "2018-09-27" nil] + [#t "2018-09-28" 0] + [#t "2018-09-29" 0] + [#t "2018-09-30" 11] + [#t "2018-10-01" 14] + [#t "2018-10-02" 8] + [#t "2018-10-03" 14] + [#t "2018-10-04" nil] + [#t "2018-10-05" 6] + [#t "2018-10-06" 4] + [#t "2018-10-07" 0] + [#t "2018-10-08" nil] + [#t "2018-10-09" 3] + [#t "2018-10-10" 13] + [#t "2018-10-11" nil] + [#t "2018-10-12" 14] + [#t "2018-10-13" 6] + [#t "2018-10-14" 12] + [#t "2018-10-15" 13] + [#t "2018-10-16" 0] + [#t "2018-10-17" 7] + [#t "2018-10-18" 10] + [#t "2018-10-19" 5]]]] diff --git a/test/metabase/test/data/dataset_definitions/office-checkins.edn b/test/metabase/test/data/dataset_definitions/office-checkins.edn index 1315f3301bc..92c42085a46 100644 --- a/test/metabase/test/data/dataset_definitions/office-checkins.edn +++ b/test/metabase/test/data/dataset_definitions/office-checkins.edn @@ -1,12 +1,12 @@ [["checkins" [{:field-name "person", :base-type :type/Text} {:field-name "timestamp", :base-type :type/DateTime}] - [["Cam", #inst "2019-01-02T05:30:00.000-07:00"] - ["Cam", #inst "2019-01-09T05:30:00.000-07:00"] - ["Kyle", #inst "2019-01-06T08:30:00.000-07:00"] - ["Cam", #inst "2019-01-07T04:00:00.000-07:00"] - ["Sameer", #inst "2019-01-26T16:00:00.000-07:00"] - ["Cam", #inst "2019-01-16T07:15:00.000-07:00"] - ["Tom", #inst "2019-01-27T01:30:00.000-07:00"] - ["Sameer", #inst "2019-01-24T14:00:00.000-07:00"] - ["Maz", #inst "2019-01-28T11:45:00.000-07:00"] - ["Cam", #inst "2019-01-25T07:30:00.000-07:00"]]]] + [["Cam", #t "2019-01-02T05:30:00.000-07:00"] + ["Cam", #t "2019-01-09T05:30:00.000-07:00"] + ["Kyle", #t "2019-01-06T08:30:00.000-07:00"] + ["Cam", #t "2019-01-07T04:00:00.000-07:00"] + ["Sameer", #t "2019-01-26T16:00:00.000-07:00"] + ["Cam", #t "2019-01-16T07:15:00.000-07:00"] + ["Tom", #t "2019-01-27T01:30:00.000-07:00"] + ["Sameer", #t "2019-01-24T14:00:00.000-07:00"] + ["Maz", #t "2019-01-28T11:45:00.000-07:00"] + ["Cam", #t "2019-01-25T07:30:00.000-07:00"]]]] diff --git a/test/metabase/test/data/dataset_definitions/test-data.edn b/test/metabase/test/data/dataset_definitions/test-data.edn index 827b10fa0a0..877eb8c623f 100644 --- a/test/metabase/test/data/dataset_definitions/test-data.edn +++ b/test/metabase/test/data/dataset_definitions/test-data.edn @@ -29,21 +29,21 @@ {:field-name "password" :base-type :type/Text :visibility-type :sensitive}] - [["Plato Yeshua" #inst "2014-04-01T08:30" "4be68cda-6fd5-4ba7-944e-2b475600bda5"] ; 1 - ["Felipinho Asklepios" #inst "2014-12-05T15:15" "5bb19ad9-f3f8-421f-9750-7d398e38428d"] ; 2 - ["Kaneonuskatew Eiran" #inst "2014-11-06T16:15" "a329ccfe-b99c-42eb-9c93-cb9adc3eb1ab"] ; 3 - ["Simcha Yan" #inst "2014-01-01T08:30" "a61f97c6-4484-4a63-b37e-b5e58bfa2ecb"] ; 4 - ["Quentin Sören" #inst "2014-10-03T17:30" "10a0fea8-9bb4-48fe-a336-4d9cbbd78aa0"] ; 5 - ["Shad Ferdynand" #inst "2014-08-02T12:30" "d35c9d78-f9cf-4f52-b1cc-cb9078eebdcb"] ; 6 - ["Conchúr Tihomir" #inst "2014-08-02T09:30" "900335ad-e03b-4259-abc7-76aac21cedca"] ; 7 - ["Szymon Theutrich" #inst "2014-02-01T10:15" "d6c47a54-9d88-4c4a-8054-ace76764ed0d"] ; 8 - ["Nils Gotam" #inst "2014-04-03T09:30" "b085040c-7aa4-4e96-8c8f-420b2c99c920"] ; 9 - ["Frans Hevel" #inst "2014-07-03T19:30" "b7a43e91-9fb9-4fe9-ab6f-ea51ab0f94e4"] ; 10 - ["Spiros Teofil" #inst "2014-11-01T07:00" "62b9602c-27b8-44ea-adbd-2748f26537af"] ; 11 - ["Kfir Caj" #inst "2014-07-03T01:30" "dfe21df3-f364-479d-a5e7-04bc5d85ad2b"] ; 12 - ["Dwight Gresham" #inst "2014-08-01T10:30" "75a1ebf1-cae7-4a50-8743-32d97500f2cf"] ; 13 - ["Broen Olujimi" #inst "2014-10-03T13:45" "f9b65c74-9f91-4cfd-9248-94a53af82866"] ; 14 - ["Rüstem Hebel" #inst "2014-08-01T12:45" "02ad6b15-54b0-4491-bf0f-d781b0a2c4f5"]]] ; 15 + [["Plato Yeshua" #t "2014-04-01T08:30" "4be68cda-6fd5-4ba7-944e-2b475600bda5"] ; 1 + ["Felipinho Asklepios" #t "2014-12-05T15:15" "5bb19ad9-f3f8-421f-9750-7d398e38428d"] ; 2 + ["Kaneonuskatew Eiran" #t "2014-11-06T16:15" "a329ccfe-b99c-42eb-9c93-cb9adc3eb1ab"] ; 3 + ["Simcha Yan" #t "2014-01-01T08:30" "a61f97c6-4484-4a63-b37e-b5e58bfa2ecb"] ; 4 + ["Quentin Sören" #t "2014-10-03T17:30" "10a0fea8-9bb4-48fe-a336-4d9cbbd78aa0"] ; 5 + ["Shad Ferdynand" #t "2014-08-02T12:30" "d35c9d78-f9cf-4f52-b1cc-cb9078eebdcb"] ; 6 + ["Conchúr Tihomir" #t "2014-08-02T09:30" "900335ad-e03b-4259-abc7-76aac21cedca"] ; 7 + ["Szymon Theutrich" #t "2014-02-01T10:15" "d6c47a54-9d88-4c4a-8054-ace76764ed0d"] ; 8 + ["Nils Gotam" #t "2014-04-03T09:30" "b085040c-7aa4-4e96-8c8f-420b2c99c920"] ; 9 + ["Frans Hevel" #t "2014-07-03T19:30" "b7a43e91-9fb9-4fe9-ab6f-ea51ab0f94e4"] ; 10 + ["Spiros Teofil" #t "2014-11-01T07:00" "62b9602c-27b8-44ea-adbd-2748f26537af"] ; 11 + ["Kfir Caj" #t "2014-07-03T01:30" "dfe21df3-f364-479d-a5e7-04bc5d85ad2b"] ; 12 + ["Dwight Gresham" #t "2014-08-01T10:30" "75a1ebf1-cae7-4a50-8743-32d97500f2cf"] ; 13 + ["Broen Olujimi" #t "2014-10-03T13:45" "f9b65c74-9f91-4cfd-9248-94a53af82866"] ; 14 + ["Rüstem Hebel" #t "2014-08-01T12:45" "02ad6b15-54b0-4491-bf0f-d781b0a2c4f5"]]] ; 15 ["categories" [{:field-name "name" :base-type :type/Text}] [["African"] ; 1 @@ -243,1003 +243,1003 @@ :fk :venues} {:field-name "date" :base-type :type/Date}] - [[5 12 #inst "2014-04-07"] - [1 31 #inst "2014-09-18"] - [8 56 #inst "2014-09-15"] - [5 4 #inst "2014-03-11"] - [3 49 #inst "2013-05-05"] - [3 35 #inst "2015-07-04"] - [12 6 #inst "2014-04-11"] - [11 16 #inst "2014-05-13"] - [3 79 #inst "2014-05-26"] - [3 55 #inst "2015-08-22"] - [15 23 #inst "2013-03-25"] - [5 25 #inst "2014-11-16"] - [6 80 #inst "2014-05-17"] - [9 93 #inst "2015-09-07"] - [5 61 #inst "2015-02-19"] - [11 34 #inst "2015-02-19"] - [8 69 #inst "2014-08-31"] - [3 27 #inst "2015-08-05"] - [11 70 #inst "2014-07-31"] - [11 81 #inst "2014-09-14"] - [4 73 #inst "2015-12-10"] - [8 18 #inst "2015-02-17"] - [8 71 #inst "2014-04-02"] - [12 45 #inst "2014-04-04"] - [12 31 #inst "2014-07-05"] - [8 67 #inst "2014-05-17"] - [11 57 #inst "2015-02-15"] - [10 100 #inst "2015-05-02"] - [15 7 #inst "2014-09-27"] - [5 48 #inst "2014-07-20"] - [8 27 #inst "2013-05-12"] - [14 31 #inst "2014-02-17"] - [5 6 #inst "2015-10-07"] - [14 28 #inst "2014-09-26"] - [10 56 #inst "2014-07-19"] - [8 19 #inst "2015-09-29"] - [4 48 #inst "2015-11-19"] - [10 11 #inst "2015-11-15"] - [11 65 #inst "2015-04-30"] - [6 6 #inst "2014-09-14"] - [10 62 #inst "2013-07-05"] - [11 88 #inst "2015-03-05"] - [5 51 #inst "2015-11-30"] - [2 61 #inst "2013-11-19"] - [9 59 #inst "2013-08-24"] - [9 66 #inst "2015-06-26"] - [14 67 #inst "2014-07-19"] - [12 15 #inst "2015-07-18"] - [5 12 #inst "2015-04-07"] - [8 13 #inst "2013-08-02"] - [13 30 #inst "2014-06-28"] - [4 8 #inst "2014-10-13"] - [6 65 #inst "2014-05-05"] - [4 93 #inst "2015-05-08"] - [15 14 #inst "2014-12-22"] - [3 99 #inst "2014-07-24"] - [2 15 #inst "2015-03-06"] - [4 40 #inst "2015-11-09"] - [13 24 #inst "2014-06-11"] - [10 69 #inst "2014-03-24"] - [3 15 #inst "2013-11-01"] - [10 3 #inst "2013-05-28"] - [5 15 #inst "2015-12-24"] - [6 34 #inst "2014-08-18"] - [7 29 #inst "2014-04-13"] - [6 86 #inst "2015-02-21"] - [3 28 #inst "2014-05-25"] - [5 29 #inst "2014-09-16"] - [8 85 #inst "2014-03-14"] - [11 65 #inst "2014-11-20"] - [14 93 #inst "2014-01-07"] - [1 1 #inst "2015-04-18"] - [11 75 #inst "2013-08-07"] - [11 91 #inst "2015-11-14"] - [7 97 #inst "2015-09-11"] - [9 9 #inst "2014-03-28"] - [14 33 #inst "2014-03-03"] - [4 3 #inst "2015-03-02"] - [8 19 #inst "2014-05-07"] - [1 99 #inst "2013-12-27"] - [7 18 #inst "2013-07-23"] - [13 30 #inst "2014-06-28"] - [11 17 #inst "2015-02-19"] - [12 58 #inst "2015-12-02"] - [13 82 #inst "2014-11-15"] - [8 69 #inst "2015-07-01"] - [14 95 #inst "2014-08-18"] - [6 75 #inst "2015-05-29"] - [14 83 #inst "2013-10-29"] - [7 66 #inst "2014-02-10"] - [11 73 #inst "2015-04-09"] - [4 93 #inst "2014-08-02"] - [2 18 #inst "2013-07-02"] - [10 17 #inst "2015-02-09"] - [8 60 #inst "2013-10-27"] - [13 24 #inst "2014-02-11"] - [2 71 #inst "2014-11-25"] - [11 29 #inst "2014-01-04"] - [13 91 #inst "2015-11-27"] - [9 21 #inst "2014-07-26"] - [11 99 #inst "2015-09-04"] - [5 44 #inst "2013-12-02"] - [8 51 #inst "2015-04-10"] - [8 9 #inst "2015-09-26"] - [11 19 #inst "2014-12-01"] - [2 38 #inst "2014-02-10"] - [10 12 #inst "2014-07-14"] - [10 30 #inst "2013-12-15"] - [8 33 #inst "2013-03-16"] - [9 49 #inst "2015-09-17"] - [6 38 #inst "2013-05-19"] - [15 93 #inst "2014-03-12"] - [2 36 #inst "2013-01-27"] - [7 98 #inst "2015-09-17"] - [4 6 #inst "2015-09-18"] - [6 34 #inst "2013-09-16"] - [11 73 #inst "2014-02-20"] - [14 46 #inst "2014-07-05"] - [1 44 #inst "2014-10-18"] - [10 83 #inst "2013-12-22"] - [3 21 #inst "2014-11-05"] - [12 57 #inst "2014-12-19"] - [10 77 #inst "2015-07-25"] - [10 97 #inst "2013-08-05"] - [14 8 #inst "2015-04-16"] - [12 13 #inst "2015-09-11"] - [15 81 #inst "2015-10-29"] - [13 17 #inst "2014-08-18"] - [15 2 #inst "2014-08-25"] - [8 74 #inst "2014-08-11"] - [7 90 #inst "2013-02-13"] - [4 84 #inst "2014-08-26"] - [10 87 #inst "2014-01-09"] - [8 88 #inst "2013-08-21"] - [6 85 #inst "2015-12-26"] - [8 62 #inst "2014-05-21"] - [4 97 #inst "2015-02-15"] - [4 65 #inst "2014-02-11"] - [9 1 #inst "2015-10-08"] - [13 96 #inst "2014-10-10"] - [10 83 #inst "2015-11-22"] - [15 24 #inst "2013-07-24"] - [10 38 #inst "2014-09-14"] - [12 3 #inst "2015-11-13"] - [4 78 #inst "2014-10-13"] - [14 12 #inst "2014-07-17"] - [3 18 #inst "2014-05-31"] - [11 10 #inst "2013-10-19"] - [11 81 #inst "2013-03-13"] - [5 61 #inst "2014-11-21"] - [13 81 #inst "2014-12-17"] - [13 24 #inst "2014-09-04"] - [8 54 #inst "2013-02-21"] - [10 42 #inst "2014-07-08"] - [7 97 #inst "2014-09-23"] - [3 14 #inst "2013-03-08"] - [12 58 #inst "2014-01-31"] - [11 100 #inst "2015-09-04"] - [8 90 #inst "2014-03-06"] - [12 20 #inst "2015-05-05"] - [3 47 #inst "2014-06-30"] - [5 44 #inst "2015-01-04"] - [1 47 #inst "2013-09-10"] - [7 11 #inst "2015-05-22"] - [15 87 #inst "2013-12-29"] - [2 66 #inst "2014-07-17"] - [2 52 #inst "2014-07-09"] - [11 3 #inst "2015-06-29"] - [9 12 #inst "2013-09-16"] - [2 39 #inst "2014-07-17"] - [11 3 #inst "2014-03-13"] - [10 47 #inst "2015-03-04"] - [15 78 #inst "2015-08-21"] - [12 3 #inst "2014-04-19"] - [11 45 #inst "2014-11-07"] - [2 75 #inst "2013-05-03"] - [10 9 #inst "2013-03-08"] - [2 51 #inst "2013-10-04"] - [4 4 #inst "2014-07-10"] - [8 57 #inst "2014-07-16"] - [8 38 #inst "2014-08-15"] - [5 31 #inst "2014-01-06"] - [3 54 #inst "2015-05-27"] - [10 84 #inst "2015-07-05"] - [5 54 #inst "2013-03-01"] - [3 43 #inst "2015-08-06"] - [8 70 #inst "2014-11-09"] - [2 59 #inst "2014-07-30"] - [11 18 #inst "2015-01-14"] - [9 75 #inst "2013-06-30"] - [13 41 #inst "2015-08-01"] - [7 87 #inst "2014-02-06"] - [6 91 #inst "2013-03-19"] - [7 90 #inst "2013-06-23"] - [8 61 #inst "2014-04-11"] - [5 35 #inst "2014-10-28"] - [2 82 #inst "2014-06-09"] - [9 35 #inst "2013-10-23"] - [6 52 #inst "2014-09-28"] - [6 96 #inst "2015-09-04"] - [11 59 #inst "2015-09-18"] - [12 34 #inst "2015-08-09"] - [10 80 #inst "2015-04-08"] - [4 78 #inst "2015-03-01"] - [6 4 #inst "2015-09-01"] - [4 35 #inst "2014-07-07"] - [13 50 #inst "2013-06-23"] - [11 69 #inst "2014-03-17"] - [10 62 #inst "2013-03-15"] - [13 31 #inst "2015-03-27"] - [13 42 #inst "2014-10-02"] - [14 86 #inst "2013-05-14"] - [4 83 #inst "2014-08-13"] - [9 21 #inst "2015-04-18"] - [14 90 #inst "2014-06-15"] - [12 65 #inst "2015-05-05"] - [7 57 #inst "2013-03-08"] - [12 70 #inst "2014-09-06"] - [11 16 #inst "2014-02-09"] - [7 38 #inst "2013-10-12"] - [2 40 #inst "2014-03-09"] - [8 52 #inst "2015-06-22"] - [7 84 #inst "2013-01-22"] - [9 4 #inst "2014-08-20"] - [2 4 #inst "2014-03-04"] - [8 56 #inst "2014-02-03"] - [6 23 #inst "2013-10-29"] - [7 87 #inst "2013-10-02"] - [5 28 #inst "2014-02-14"] - [5 19 #inst "2013-09-08"] - [2 13 #inst "2014-06-30"] - [12 65 #inst "2013-07-25"] - [4 73 #inst "2015-11-06"] - [7 56 #inst "2013-07-14"] - [1 46 #inst "2014-03-09"] - [13 58 #inst "2013-07-10"] - [4 68 #inst "2013-04-12"] - [14 86 #inst "2014-03-09"] - [7 89 #inst "2014-11-22"] - [4 42 #inst "2014-04-13"] - [13 83 #inst "2014-10-19"] - [10 66 #inst "2014-07-07"] - [11 69 #inst "2013-08-19"] - [2 18 #inst "2014-11-28"] - [12 7 #inst "2015-08-16"] - [7 45 #inst "2014-03-20"] - [8 85 #inst "2014-10-09"] - [13 27 #inst "2014-05-16"] - [8 6 #inst "2014-08-24"] - [9 52 #inst "2013-04-11"] - [2 75 #inst "2015-02-26"] - [11 65 #inst "2014-05-29"] - [7 30 #inst "2013-06-03"] - [11 14 #inst "2013-06-26"] - [11 61 #inst "2014-11-09"] - [8 81 #inst "2013-06-27"] - [10 92 #inst "2014-05-07"] - [3 52 #inst "2014-01-26"] - [5 56 #inst "2014-11-14"] - [11 75 #inst "2014-04-02"] - [9 13 #inst "2014-10-22"] - [4 25 #inst "2015-05-18"] - [10 70 #inst "2015-05-04"] - [2 48 #inst "2014-06-21"] - [6 78 #inst "2014-03-28"] - [12 68 #inst "2014-10-24"] - [10 8 #inst "2014-02-26"] - [5 63 #inst "2015-10-12"] - [14 66 #inst "2015-08-06"] - [2 3 #inst "2014-05-14"] - [3 36 #inst "2014-04-27"] - [11 71 #inst "2015-04-24"] - [1 85 #inst "2015-03-02"] - [13 68 #inst "2015-06-22"] - [5 34 #inst "2014-09-25"] - [2 75 #inst "2014-07-29"] - [7 13 #inst "2014-10-03"] - [12 86 #inst "2014-01-10"] - [13 100 #inst "2015-07-17"] - [8 59 #inst "2014-10-15"] - [15 27 #inst "2014-05-17"] - [13 83 #inst "2013-10-11"] - [2 68 #inst "2014-06-03"] - [10 23 #inst "2013-04-08"] - [12 17 #inst "2013-01-10"] - [8 89 #inst "2014-05-01"] - [4 11 #inst "2013-06-12"] - [3 97 #inst "2015-05-22"] - [14 77 #inst "2013-10-19"] - [10 69 #inst "2014-10-10"] - [13 79 #inst "2014-11-11"] - [5 95 #inst "2014-07-22"] - [14 33 #inst "2015-08-26"] - [2 75 #inst "2014-07-10"] - [7 97 #inst "2014-03-01"] - [6 88 #inst "2014-08-04"] - [12 73 #inst "2013-11-16"] - [14 61 #inst "2013-05-21"] - [9 43 #inst "2015-03-07"] - [4 44 #inst "2013-08-20"] - [15 61 #inst "2015-07-18"] - [11 98 #inst "2014-09-29"] - [11 32 #inst "2013-08-02"] - [3 94 #inst "2014-12-06"] - [4 48 #inst "2015-08-20"] - [11 59 #inst "2014-07-21"] - [9 77 #inst "2014-06-05"] - [7 10 #inst "2015-04-23"] - [7 17 #inst "2013-04-01"] - [9 45 #inst "2015-02-13"] - [3 2 #inst "2014-12-23"] - [9 85 #inst "2014-12-11"] - [6 22 #inst "2015-04-24"] - [12 96 #inst "2013-06-11"] - [1 78 #inst "2014-06-09"] - [13 29 #inst "2014-02-10"] - [3 54 #inst "2013-01-19"] - [11 60 #inst "2014-08-30"] - [2 16 #inst "2013-11-27"] - [9 41 #inst "2014-05-14"] - [10 98 #inst "2014-01-26"] - [13 98 #inst "2015-07-01"] - [12 4 #inst "2015-10-04"] - [1 63 #inst "2014-03-14"] - [11 2 #inst "2015-10-23"] - [14 64 #inst "2014-05-27"] - [6 42 #inst "2014-05-01"] - [2 44 #inst "2014-09-26"] - [5 42 #inst "2013-08-15"] - [5 39 #inst "2013-04-26"] - [11 94 #inst "2014-07-14"] - [4 17 #inst "2015-08-18"] - [9 3 #inst "2014-02-19"] - [3 75 #inst "2014-05-18"] - [10 38 #inst "2015-09-22"] - [10 74 #inst "2013-03-28"] - [11 25 #inst "2014-01-04"] - [7 20 #inst "2014-09-14"] - [7 33 #inst "2014-05-18"] - [2 40 #inst "2013-02-19"] - [9 62 #inst "2013-06-16"] - [6 5 #inst "2014-11-25"] - [14 13 #inst "2014-07-19"] - [4 82 #inst "2013-04-24"] - [6 86 #inst "2014-04-10"] - [15 66 #inst "2013-05-31"] - [10 63 #inst "2014-09-03"] - [13 46 #inst "2014-06-25"] - [13 44 #inst "2015-02-24"] - [5 82 #inst "2014-06-19"] - [12 57 #inst "2014-09-12"] - [5 96 #inst "2015-03-16"] - [12 36 #inst "2015-05-11"] - [6 100 #inst "2015-11-28"] - [9 44 #inst "2014-10-24"] - [13 70 #inst "2014-04-03"] - [10 77 #inst "2014-04-12"] - [13 42 #inst "2015-07-23"] - [6 99 #inst "2013-06-02"] - [9 22 #inst "2015-07-06"] - [13 83 #inst "2014-12-16"] - [13 27 #inst "2014-07-20"] - [10 94 #inst "2015-11-05"] - [13 70 #inst "2015-01-23"] - [5 59 #inst "2014-05-20"] - [12 61 #inst "2013-01-25"] - [1 5 #inst "2015-07-23"] - [5 95 #inst "2013-08-19"] - [6 88 #inst "2014-07-25"] - [3 54 #inst "2013-11-07"] - [11 57 #inst "2015-07-20"] - [7 27 #inst "2014-08-26"] - [2 62 #inst "2015-05-03"] - [4 36 #inst "2014-04-14"] - [12 1 #inst "2013-11-09"] - [4 27 #inst "2013-07-14"] - [10 63 #inst "2013-11-15"] - [6 31 #inst "2014-05-22"] - [2 90 #inst "2015-10-04"] - [8 2 #inst "2013-12-03"] - [1 86 #inst "2015-09-23"] - [7 46 #inst "2014-09-05"] - [9 8 #inst "2015-07-29"] - [1 51 #inst "2014-05-30"] - [7 68 #inst "2014-03-25"] - [14 74 #inst "2015-02-23"] - [8 63 #inst "2015-05-12"] - [1 68 #inst "2013-04-11"] - [7 20 #inst "2015-03-28"] - [14 28 #inst "2014-07-24"] - [13 8 #inst "2014-02-03"] - [3 40 #inst "2013-09-27"] - [9 72 #inst "2014-01-19"] - [11 15 #inst "2013-05-14"] - [9 11 #inst "2014-03-30"] - [4 10 #inst "2014-12-03"] - [4 79 #inst "2014-11-07"] - [4 74 #inst "2013-05-09"] - [5 78 #inst "2015-12-08"] - [12 74 #inst "2015-06-04"] - [6 89 #inst "2014-06-14"] - [2 87 #inst "2013-01-23"] - [15 84 #inst "2015-07-18"] - [4 65 #inst "2015-03-11"] - [7 66 #inst "2013-07-25"] - [10 14 #inst "2013-11-29"] - [5 77 #inst "2014-06-02"] - [8 74 #inst "2013-04-30"] - [14 7 #inst "2014-03-05"] - [4 45 #inst "2013-11-05"] - [15 96 #inst "2013-11-26"] - [4 45 #inst "2015-01-15"] - [15 52 #inst "2015-05-01"] - [6 46 #inst "2014-02-25"] - [12 42 #inst "2014-11-10"] - [13 17 #inst "2014-05-20"] - [6 44 #inst "2015-03-27"] - [3 71 #inst "2014-04-14"] - [2 35 #inst "2013-10-01"] - [9 74 #inst "2015-03-03"] - [4 68 #inst "2014-12-01"] - [6 40 #inst "2013-11-25"] - [4 63 #inst "2014-06-14"] - [11 12 #inst "2013-08-05"] - [13 41 #inst "2015-02-03"] - [11 13 #inst "2014-10-05"] - [10 18 #inst "2015-03-20"] - [5 20 #inst "2014-05-11"] - [5 79 #inst "2014-02-22"] - [7 15 #inst "2013-04-15"] - [6 25 #inst "2014-03-28"] - [14 9 #inst "2014-08-12"] - [8 53 #inst "2014-04-24"] - [9 78 #inst "2014-07-02"] - [3 4 #inst "2014-06-26"] - [7 3 #inst "2015-10-29"] - [6 56 #inst "2015-10-28"] - [4 65 #inst "2014-08-12"] - [15 35 #inst "2014-07-28"] - [8 49 #inst "2014-09-01"] - [11 80 #inst "2014-07-31"] - [10 51 #inst "2015-03-01"] - [14 70 #inst "2013-07-15"] - [12 18 #inst "2013-10-06"] - [8 80 #inst "2013-10-31"] - [15 91 #inst "2013-11-16"] - [9 78 #inst "2014-09-06"] - [9 88 #inst "2013-06-04"] - [12 88 #inst "2014-05-29"] - [7 22 #inst "2013-06-07"] - [2 38 #inst "2014-06-21"] - [4 7 #inst "2014-05-07"] - [2 49 #inst "2013-04-18"] - [13 56 #inst "2014-07-19"] - [9 66 #inst "2013-06-14"] - [9 57 #inst "2014-07-29"] - [5 91 #inst "2014-04-04"] - [10 46 #inst "2015-06-08"] - [10 97 #inst "2014-08-01"] - [2 53 #inst "2014-07-04"] - [14 54 #inst "2015-07-27"] - [2 81 #inst "2013-08-17"] - [11 77 #inst "2015-07-12"] - [13 39 #inst "2013-08-03"] - [7 86 #inst "2014-01-16"] - [14 68 #inst "2014-05-07"] - [13 61 #inst "2014-05-29"] - [6 90 #inst "2015-09-16"] - [11 59 #inst "2014-10-13"] - [11 41 #inst "2015-11-07"] - [12 2 #inst "2015-04-23"] - [10 76 #inst "2013-10-18"] - [14 77 #inst "2014-02-04"] - [2 80 #inst "2014-01-04"] - [3 65 #inst "2015-08-15"] - [9 59 #inst "2013-04-03"] - [11 6 #inst "2015-11-10"] - [9 29 #inst "2013-06-30"] - [10 37 #inst "2014-06-27"] - [2 26 #inst "2013-12-11"] - [3 28 #inst "2013-03-05"] - [2 94 #inst "2015-03-13"] - [11 72 #inst "2015-10-05"] - [7 39 #inst "2014-08-15"] - [9 6 #inst "2014-05-18"] - [10 98 #inst "2013-04-26"] - [9 92 #inst "2014-02-18"] - [13 39 #inst "2014-08-09"] - [9 21 #inst "2014-08-12"] - [2 60 #inst "2014-01-27"] - [6 58 #inst "2013-07-22"] - [8 41 #inst "2014-05-16"] - [6 15 #inst "2014-08-30"] - [12 7 #inst "2015-04-21"] - [7 14 #inst "2014-09-01"] - [10 43 #inst "2014-11-27"] - [14 22 #inst "2014-05-22"] - [2 48 #inst "2015-11-22"] - [9 92 #inst "2014-02-12"] - [8 48 #inst "2015-10-26"] - [13 97 #inst "2015-05-09"] - [6 12 #inst "2014-07-14"] - [11 22 #inst "2013-06-17"] - [4 23 #inst "2013-03-23"] - [10 68 #inst "2014-06-22"] - [5 78 #inst "2014-07-16"] - [4 32 #inst "2015-03-01"] - [10 33 #inst "2014-05-23"] - [10 6 #inst "2013-07-07"] - [7 98 #inst "2015-04-04"] - [14 29 #inst "2015-02-03"] - [2 53 #inst "2014-09-08"] - [9 43 #inst "2014-07-30"] - [14 74 #inst "2015-11-01"] - [3 94 #inst "2014-09-27"] - [11 86 #inst "2015-09-27"] - [1 50 #inst "2014-11-07"] - [4 43 #inst "2013-06-19"] - [2 85 #inst "2015-07-28"] - [5 24 #inst "2014-11-09"] - [14 88 #inst "2014-08-26"] - [6 61 #inst "2014-08-09"] - [2 83 #inst "2015-12-19"] - [1 38 #inst "2015-07-25"] - [6 49 #inst "2015-01-25"] - [12 31 #inst "2015-02-09"] - [6 61 #inst "2014-11-28"] - [5 50 #inst "2013-06-12"] - [7 81 #inst "2014-11-03"] - [9 48 #inst "2014-03-27"] - [6 72 #inst "2014-09-24"] - [4 59 #inst "2013-10-06"] - [8 48 #inst "2014-04-18"] - [11 88 #inst "2015-04-10"] - [10 67 #inst "2014-02-28"] - [2 74 #inst "2014-01-18"] - [10 70 #inst "2014-12-07"] - [4 53 #inst "2014-11-07"] - [8 81 #inst "2015-02-18"] - [3 72 #inst "2014-05-05"] - [15 72 #inst "2014-06-17"] - [4 8 #inst "2015-06-13"] - [8 73 #inst "2014-11-30"] - [8 93 #inst "2014-09-20"] - [14 44 #inst "2014-01-21"] - [8 68 #inst "2014-06-05"] - [5 94 #inst "2013-05-20"] - [3 7 #inst "2015-05-29"] - [7 49 #inst "2013-09-10"] - [7 49 #inst "2013-07-26"] - [15 74 #inst "2015-10-26"] - [7 66 #inst "2015-07-29"] - [8 93 #inst "2015-07-07"] - [13 79 #inst "2014-11-12"] - [6 7 #inst "2014-12-27"] - [3 80 #inst "2015-06-22"] - [13 6 #inst "2014-09-09"] - [3 82 #inst "2015-06-27"] - [12 13 #inst "2013-06-29"] - [14 86 #inst "2014-01-07"] - [5 66 #inst "2014-05-26"] - [14 62 #inst "2013-08-18"] - [10 97 #inst "2013-11-19"] - [6 94 #inst "2013-04-19"] - [2 41 #inst "2014-03-03"] - [13 74 #inst "2014-05-26"] - [7 63 #inst "2014-05-28"] - [14 31 #inst "2013-12-04"] - [13 41 #inst "2013-06-15"] - [12 51 #inst "2015-12-26"] - [4 65 #inst "2015-12-18"] - [5 64 #inst "2013-08-02"] - [12 18 #inst "2013-10-24"] - [4 38 #inst "2014-04-26"] - [7 30 #inst "2014-09-18"] - [5 17 #inst "2014-05-18"] - [2 76 #inst "2015-09-04"] - [13 42 #inst "2015-05-26"] - [9 74 #inst "2014-08-01"] - [7 42 #inst "2013-06-21"] - [3 26 #inst "2015-09-28"] - [4 27 #inst "2013-05-14"] - [12 21 #inst "2013-05-26"] - [13 20 #inst "2015-07-15"] - [2 85 #inst "2014-05-02"] - [7 52 #inst "2014-10-21"] - [5 3 #inst "2014-05-04"] - [5 79 #inst "2014-07-11"] - [3 10 #inst "2014-05-31"] - [9 2 #inst "2015-01-28"] - [3 85 #inst "2013-11-13"] - [5 40 #inst "2015-09-11"] - [11 70 #inst "2015-09-20"] - [5 86 #inst "2014-12-05"] - [3 86 #inst "2014-04-24"] - [5 52 #inst "2014-11-05"] - [9 72 #inst "2013-11-22"] - [8 27 #inst "2015-09-28"] - [8 48 #inst "2014-08-02"] - [1 35 #inst "2014-05-26"] - [11 6 #inst "2014-10-16"] - [1 58 #inst "2013-11-18"] - [8 90 #inst "2014-08-03"] - [5 47 #inst "2013-09-02"] - [11 88 #inst "2013-12-11"] - [3 71 #inst "2014-09-26"] - [14 66 #inst "2015-06-13"] - [6 27 #inst "2015-08-16"] - [4 42 #inst "2015-01-30"] - [10 67 #inst "2014-12-09"] - [3 75 #inst "2015-10-08"] - [9 68 #inst "2013-11-09"] - [9 87 #inst "2014-11-08"] - [5 12 #inst "2014-02-05"] - [13 87 #inst "2013-04-23"] - [3 72 #inst "2015-05-25"] - [3 95 #inst "2015-12-18"] - [4 43 #inst "2013-04-14"] - [6 17 #inst "2014-06-28"] - [12 32 #inst "2014-01-05"] - [14 96 #inst "2013-04-13"] - [1 76 #inst "2015-10-29"] - [5 93 #inst "2014-08-21"] - [14 53 #inst "2013-11-18"] - [14 20 #inst "2014-10-25"] - [3 91 #inst "2015-10-19"] - [8 8 #inst "2015-11-21"] - [13 34 #inst "2013-08-20"] - [2 54 #inst "2014-05-08"] - [3 66 #inst "2014-10-16"] - [3 57 #inst "2014-09-16"] - [10 12 #inst "2015-04-12"] - [10 93 #inst "2014-01-04"] - [6 20 #inst "2014-03-18"] - [14 50 #inst "2015-08-20"] - [7 35 #inst "2014-07-24"] - [9 25 #inst "2014-07-08"] - [13 43 #inst "2014-12-23"] - [3 43 #inst "2014-10-06"] - [3 58 #inst "2014-06-10"] - [8 59 #inst "2013-07-05"] - [8 9 #inst "2014-03-02"] - [12 11 #inst "2013-11-12"] - [8 82 #inst "2014-12-19"] - [3 88 #inst "2014-03-23"] - [10 81 #inst "2015-07-01"] - [4 31 #inst "2014-05-01"] - [1 10 #inst "2013-03-12"] - [7 98 #inst "2015-04-21"] - [10 69 #inst "2013-05-03"] - [4 7 #inst "2014-11-09"] - [11 57 #inst "2014-06-05"] - [4 75 #inst "2013-08-20"] - [10 8 #inst "2014-10-06"] - [9 48 #inst "2015-10-06"] - [14 38 #inst "2013-04-14"] - [6 41 #inst "2014-10-25"] - [5 14 #inst "2013-05-07"] - [11 38 #inst "2015-05-13"] - [3 33 #inst "2014-11-08"] - [1 72 #inst "2013-07-25"] - [10 84 #inst "2013-04-07"] - [10 24 #inst "2014-06-25"] - [3 50 #inst "2013-02-06"] - [14 18 #inst "2015-10-28"] - [7 95 #inst "2014-10-15"] - [13 86 #inst "2014-05-05"] - [14 72 #inst "2015-08-05"] - [13 24 #inst "2015-10-22"] - [10 19 #inst "2014-07-06"] - [1 26 #inst "2014-12-31"] - [9 12 #inst "2014-06-29"] - [8 32 #inst "2013-08-04"] - [3 28 #inst "2015-09-19"] - [15 37 #inst "2014-10-23"] - [8 8 #inst "2014-09-16"] - [7 100 #inst "2014-01-19"] - [8 85 #inst "2014-03-31"] - [8 23 #inst "2014-02-18"] - [4 95 #inst "2015-03-03"] - [11 93 #inst "2013-10-28"] - [6 75 #inst "2014-07-25"] - [10 18 #inst "2013-08-27"] - [14 68 #inst "2013-02-20"] - [12 13 #inst "2015-02-14"] - [4 2 #inst "2013-02-27"] - [7 81 #inst "2013-04-16"] - [3 21 #inst "2013-04-07"] - [6 43 #inst "2014-09-30"] - [5 73 #inst "2014-11-29"] - [2 38 #inst "2014-08-09"] - [14 60 #inst "2014-04-29"] - [10 90 #inst "2015-12-29"] - [7 3 #inst "2015-06-27"] - [2 18 #inst "2014-10-14"] - [4 95 #inst "2013-05-27"] - [4 65 #inst "2014-06-24"] - [10 32 #inst "2014-08-02"] - [13 72 #inst "2013-02-22"] - [4 9 #inst "2014-02-07"] - [12 49 #inst "2014-11-18"] - [11 99 #inst "2014-06-29"] - [10 30 #inst "2014-04-21"] - [12 5 #inst "2014-03-26"] - [7 56 #inst "2014-01-04"] - [9 16 #inst "2013-10-11"] - [6 44 #inst "2013-11-11"] - [2 27 #inst "2015-03-18"] - [12 25 #inst "2014-11-08"] - [1 7 #inst "2015-05-29"] - [7 91 #inst "2015-06-18"] - [6 89 #inst "2015-11-16"] - [8 12 #inst "2013-10-01"] - [5 9 #inst "2013-04-18"] - [3 81 #inst "2014-05-01"] - [7 53 #inst "2013-03-26"] - [6 45 #inst "2014-02-13"] - [8 84 #inst "2015-04-20"] - [5 2 #inst "2013-10-02"] - [8 7 #inst "2014-09-10"] - [15 41 #inst "2013-07-19"] - [13 18 #inst "2014-07-24"] - [14 54 #inst "2015-09-18"] - [11 84 #inst "2014-08-13"] - [7 56 #inst "2014-03-29"] - [13 37 #inst "2014-05-21"] - [4 96 #inst "2014-04-30"] - [6 76 #inst "2014-09-16"] - [5 21 #inst "2014-07-08"] - [8 61 #inst "2014-03-10"] - [5 26 #inst "2014-09-05"] - [8 100 #inst "2013-05-29"] - [3 47 #inst "2014-05-08"] - [7 46 #inst "2015-10-04"] - [5 73 #inst "2014-02-10"] - [1 54 #inst "2014-02-08"] - [12 46 #inst "2014-06-29"] - [14 46 #inst "2014-10-16"] - [10 69 #inst "2015-10-29"] - [1 39 #inst "2013-06-03"] - [3 23 #inst "2014-03-09"] - [10 43 #inst "2014-07-13"] - [14 95 #inst "2014-04-17"] - [10 75 #inst "2014-03-17"] - [4 50 #inst "2013-02-18"] - [12 43 #inst "2013-11-01"] - [9 33 #inst "2015-07-02"] - [4 91 #inst "2013-04-02"] - [15 16 #inst "2014-04-12"] - [3 42 #inst "2014-02-10"] - [12 65 #inst "2014-03-20"] - [13 72 #inst "2015-07-22"] - [13 86 #inst "2015-05-01"] - [13 93 #inst "2013-03-19"] - [10 49 #inst "2013-12-19"] - [13 8 #inst "2014-12-05"] - [15 52 #inst "2015-08-09"] - [7 95 #inst "2013-12-11"] - [9 90 #inst "2014-10-10"] - [8 50 #inst "2015-03-05"] - [6 11 #inst "2014-01-12"] - [13 26 #inst "2014-08-25"] - [3 39 #inst "2014-10-14"] - [8 36 #inst "2015-11-13"] - [5 97 #inst "2014-05-20"] - [10 35 #inst "2014-05-07"] - [11 74 #inst "2015-04-06"] - [15 75 #inst "2013-04-28"] - [2 88 #inst "2014-01-18"] - [9 58 #inst "2014-04-16"] - [6 41 #inst "2014-11-05"] - [10 44 #inst "2015-04-11"] - [10 64 #inst "2013-07-20"] - [10 19 #inst "2014-02-12"] - [4 13 #inst "2014-03-01"] - [13 27 #inst "2014-04-02"] - [15 33 #inst "2013-03-28"] - [3 6 #inst "2015-09-05"] - [7 63 #inst "2014-03-08"] - [12 94 #inst "2014-09-23"] - [7 38 #inst "2014-04-03"] - [11 85 #inst "2014-02-17"] - [9 76 #inst "2014-07-13"] - [8 83 #inst "2014-05-28"] - [14 42 #inst "2015-02-03"] - [4 35 #inst "2014-03-25"] - [7 58 #inst "2014-03-25"] - [3 54 #inst "2014-02-25"] - [5 60 #inst "2014-12-16"] - [9 100 #inst "2014-05-20"] - [12 6 #inst "2014-04-09"] - [3 76 #inst "2013-07-29"] - [8 73 #inst "2013-04-26"] - [13 33 #inst "2014-11-03"] - [6 45 #inst "2014-05-17"] - [5 87 #inst "2014-10-07"] - [5 90 #inst "2015-07-21"] - [9 36 #inst "2015-08-26"] - [7 57 #inst "2015-05-21"] - [9 20 #inst "2013-10-03"] - [4 13 #inst "2013-05-18"] - [13 63 #inst "2014-03-22"] - [5 42 #inst "2015-08-22"] - [9 49 #inst "2015-03-02"] - [6 15 #inst "2014-10-02"] - [7 17 #inst "2013-07-18"] - [11 63 #inst "2014-02-17"] - [3 90 #inst "2013-02-26"] - [13 90 #inst "2013-06-08"] - [6 46 #inst "2014-03-24"] - [15 91 #inst "2014-02-19"] - [10 65 #inst "2014-10-10"] - [3 76 #inst "2014-05-08"] - [13 43 #inst "2014-02-11"] - [7 15 #inst "2015-07-09"] - [1 36 #inst "2014-03-03"] - [9 59 #inst "2014-08-03"] - [5 86 #inst "2015-04-02"] - [14 63 #inst "2014-07-11"] - [5 94 #inst "2013-11-24"] - [14 54 #inst "2014-08-03"] - [2 37 #inst "2014-08-02"] - [4 62 #inst "2015-11-08"] - [7 17 #inst "2013-10-01"] - [13 59 #inst "2014-01-03"] - [4 22 #inst "2013-03-14"] - [3 94 #inst "2015-12-16"] - [14 89 #inst "2014-06-06"] - [15 23 #inst "2015-08-19"] - [8 12 #inst "2015-03-17"] - [8 93 #inst "2015-05-29"] - [3 20 #inst "2013-05-20"] - [9 2 #inst "2013-04-03"] - [13 73 #inst "2014-06-30"] - [10 5 #inst "2015-05-04"] - [4 98 #inst "2014-08-29"] - [5 38 #inst "2015-04-15"] - [3 41 #inst "2014-10-19"] - [2 53 #inst "2013-06-21"] - [12 97 #inst "2015-11-03"] - [15 68 #inst "2013-05-06"] - [15 22 #inst "2013-08-16"] - [11 57 #inst "2013-05-01"] - [6 91 #inst "2015-02-22"] - [2 63 #inst "2014-04-22"] - [13 70 #inst "2013-03-06"] - [11 86 #inst "2013-11-02"] - [13 23 #inst "2015-05-26"] - [12 5 #inst "2013-11-17"] - [5 43 #inst "2015-12-02"] - [11 96 #inst "2014-03-26"] - [2 90 #inst "2013-12-03"] - [10 21 #inst "2014-08-24"] - [11 20 #inst "2014-10-28"] - [13 44 #inst "2015-09-11"] - [11 57 #inst "2014-08-02"] - [6 45 #inst "2014-09-29"] - [13 68 #inst "2014-08-03"] - [9 75 #inst "2015-07-24"] - [7 37 #inst "2014-10-06"] - [6 21 #inst "2014-11-06"] - [14 49 #inst "2013-11-22"] - [7 67 #inst "2014-08-03"] - [4 75 #inst "2014-10-20"] - [13 13 #inst "2015-08-26"] - [2 58 #inst "2014-09-01"] - [2 23 #inst "2013-03-19"] - [4 38 #inst "2014-10-03"] - [7 26 #inst "2015-10-06"] - [2 93 #inst "2014-10-23"] - [2 41 #inst "2014-07-02"] - [7 99 #inst "2014-10-18"] - [14 64 #inst "2014-09-10"] - [10 9 #inst "2014-10-25"] - [6 48 #inst "2014-12-25"] - [8 58 #inst "2014-02-18"] - [3 35 #inst "2014-08-25"] - [6 98 #inst "2014-07-01"] - [8 97 #inst "2013-09-16"] - [13 26 #inst "2014-09-22"] - [2 91 #inst "2014-04-15"] - [6 20 #inst "2015-06-30"] - [15 74 #inst "2014-06-13"] - [7 62 #inst "2014-10-13"] - [7 95 #inst "2014-06-03"] - [1 96 #inst "2014-10-16"] - [9 84 #inst "2013-10-20"] - [4 55 #inst "2014-09-24"] - [13 86 #inst "2014-02-16"] - [14 9 #inst "2015-02-05"] - [9 37 #inst "2014-06-19"] - [3 12 #inst "2015-11-13"] - [10 91 #inst "2014-06-10"] - [1 13 #inst "2013-10-29"] - [4 57 #inst "2013-05-10"] - [5 57 #inst "2014-05-28"] - [14 31 #inst "2013-06-18"] - [3 29 #inst "2014-06-16"] - [7 93 #inst "2015-06-07"] - [7 87 #inst "2015-11-21"] - [9 53 #inst "2015-09-26"] - [14 93 #inst "2014-10-20"] - [14 37 #inst "2014-08-14"] - [3 30 #inst "2013-03-21"] - [10 82 #inst "2013-06-05"] - [4 40 #inst "2015-07-17"] - [8 45 #inst "2014-09-08"] - [6 84 #inst "2013-02-15"] - [11 87 #inst "2015-11-06"] - [10 93 #inst "2014-12-24"] - [2 54 #inst "2014-08-02"] - [3 34 #inst "2014-05-07"] - [13 48 #inst "2014-10-01"] - [4 48 #inst "2014-10-24"] - [1 46 #inst "2015-04-25"] - [14 85 #inst "2015-03-15"] - [4 37 #inst "2014-03-05"] - [6 62 #inst "2014-02-20"] - [2 73 #inst "2014-08-20"] - [2 14 #inst "2013-09-29"] - [6 83 #inst "2013-09-01"] - [11 89 #inst "2013-10-16"] - [3 58 #inst "2013-12-04"] - [3 36 #inst "2014-06-22"] - [5 96 #inst "2015-06-26"] - [5 18 #inst "2014-04-22"] - [4 54 #inst "2014-10-29"] - [9 31 #inst "2013-09-29"] - [12 49 #inst "2015-04-19"] - [3 38 #inst "2013-01-26"] - [4 88 #inst "2013-01-03"] - [12 58 #inst "2015-11-25"] - [12 58 #inst "2015-08-24"] - [15 3 #inst "2015-05-22"] - [10 17 #inst "2013-05-04"] - [6 85 #inst "2013-08-10"] - [7 18 #inst "2015-07-09"] - [12 67 #inst "2015-06-15"] - [8 96 #inst "2015-02-22"] - [15 88 #inst "2015-02-13"] - [8 70 #inst "2015-12-22"] - [8 48 #inst "2014-10-04"] - [3 91 #inst "2013-06-05"] - [8 83 #inst "2014-11-06"] - [12 5 #inst "2013-11-28"] - [13 88 #inst "2014-03-29"] - [2 73 #inst "2014-11-02"] - [7 13 #inst "2013-10-22"] - [13 17 #inst "2015-06-16"] - [7 11 #inst "2014-03-09"] - [2 84 #inst "2014-03-06"] - [8 79 #inst "2014-06-13"] - [2 77 #inst "2014-04-10"] - [3 40 #inst "2014-05-11"] - [8 30 #inst "2013-03-06"] - [1 47 #inst "2014-12-07"] - [11 49 #inst "2014-12-21"] - [5 39 #inst "2014-10-31"] - [3 98 #inst "2014-10-22"] - [9 20 #inst "2015-04-09"] - [13 66 #inst "2013-07-23"] - [15 18 #inst "2013-04-26"] - [9 37 #inst "2013-02-06"] - [12 79 #inst "2014-09-07"] - [8 49 #inst "2014-04-26"] - [6 87 #inst "2015-07-01"] - [2 70 #inst "2015-09-27"] - [7 44 #inst "2014-11-05"] - [6 65 #inst "2014-11-27"] - [8 51 #inst "2015-09-07"] - [6 11 #inst "2015-08-21"] - [11 76 #inst "2014-05-21"] - [5 94 #inst "2014-09-20"] - [1 97 #inst "2015-04-05"] - [2 20 #inst "2014-11-21"] - [9 25 #inst "2014-06-03"] - [4 10 #inst "2013-09-21"] - [14 78 #inst "2013-09-14"] - [6 34 #inst "2014-05-30"] - [1 16 #inst "2014-03-30"] - [15 36 #inst "2014-09-23"] - [8 5 #inst "2013-08-21"] - [11 39 #inst "2014-10-10"] - [4 66 #inst "2014-03-16"] - [12 74 #inst "2014-10-07"] - [6 76 #inst "2015-08-09"] - [14 62 #inst "2015-07-22"] - [14 98 #inst "2015-08-13"] - [8 40 #inst "2014-04-03"] - [3 33 #inst "2014-11-13"] - [12 42 #inst "2014-05-09"] - [8 77 #inst "2015-09-24"] - [2 16 #inst "2014-12-09"] - [4 29 #inst "2015-05-29"] - [11 49 #inst "2014-03-05"] - [13 58 #inst "2014-04-29"] - [9 34 #inst "2014-05-04"] - [12 5 #inst "2015-04-16"] - [7 67 #inst "2015-02-07"] - [2 92 #inst "2014-06-03"]]]] + [[5 12 #t "2014-04-07"] + [1 31 #t "2014-09-18"] + [8 56 #t "2014-09-15"] + [5 4 #t "2014-03-11"] + [3 49 #t "2013-05-05"] + [3 35 #t "2015-07-04"] + [12 6 #t "2014-04-11"] + [11 16 #t "2014-05-13"] + [3 79 #t "2014-05-26"] + [3 55 #t "2015-08-22"] + [15 23 #t "2013-03-25"] + [5 25 #t "2014-11-16"] + [6 80 #t "2014-05-17"] + [9 93 #t "2015-09-07"] + [5 61 #t "2015-02-19"] + [11 34 #t "2015-02-19"] + [8 69 #t "2014-08-31"] + [3 27 #t "2015-08-05"] + [11 70 #t "2014-07-31"] + [11 81 #t "2014-09-14"] + [4 73 #t "2015-12-10"] + [8 18 #t "2015-02-17"] + [8 71 #t "2014-04-02"] + [12 45 #t "2014-04-04"] + [12 31 #t "2014-07-05"] + [8 67 #t "2014-05-17"] + [11 57 #t "2015-02-15"] + [10 100 #t "2015-05-02"] + [15 7 #t "2014-09-27"] + [5 48 #t "2014-07-20"] + [8 27 #t "2013-05-12"] + [14 31 #t "2014-02-17"] + [5 6 #t "2015-10-07"] + [14 28 #t "2014-09-26"] + [10 56 #t "2014-07-19"] + [8 19 #t "2015-09-29"] + [4 48 #t "2015-11-19"] + [10 11 #t "2015-11-15"] + [11 65 #t "2015-04-30"] + [6 6 #t "2014-09-14"] + [10 62 #t "2013-07-05"] + [11 88 #t "2015-03-05"] + [5 51 #t "2015-11-30"] + [2 61 #t "2013-11-19"] + [9 59 #t "2013-08-24"] + [9 66 #t "2015-06-26"] + [14 67 #t "2014-07-19"] + [12 15 #t "2015-07-18"] + [5 12 #t "2015-04-07"] + [8 13 #t "2013-08-02"] + [13 30 #t "2014-06-28"] + [4 8 #t "2014-10-13"] + [6 65 #t "2014-05-05"] + [4 93 #t "2015-05-08"] + [15 14 #t "2014-12-22"] + [3 99 #t "2014-07-24"] + [2 15 #t "2015-03-06"] + [4 40 #t "2015-11-09"] + [13 24 #t "2014-06-11"] + [10 69 #t "2014-03-24"] + [3 15 #t "2013-11-01"] + [10 3 #t "2013-05-28"] + [5 15 #t "2015-12-24"] + [6 34 #t "2014-08-18"] + [7 29 #t "2014-04-13"] + [6 86 #t "2015-02-21"] + [3 28 #t "2014-05-25"] + [5 29 #t "2014-09-16"] + [8 85 #t "2014-03-14"] + [11 65 #t "2014-11-20"] + [14 93 #t "2014-01-07"] + [1 1 #t "2015-04-18"] + [11 75 #t "2013-08-07"] + [11 91 #t "2015-11-14"] + [7 97 #t "2015-09-11"] + [9 9 #t "2014-03-28"] + [14 33 #t "2014-03-03"] + [4 3 #t "2015-03-02"] + [8 19 #t "2014-05-07"] + [1 99 #t "2013-12-27"] + [7 18 #t "2013-07-23"] + [13 30 #t "2014-06-28"] + [11 17 #t "2015-02-19"] + [12 58 #t "2015-12-02"] + [13 82 #t "2014-11-15"] + [8 69 #t "2015-07-01"] + [14 95 #t "2014-08-18"] + [6 75 #t "2015-05-29"] + [14 83 #t "2013-10-29"] + [7 66 #t "2014-02-10"] + [11 73 #t "2015-04-09"] + [4 93 #t "2014-08-02"] + [2 18 #t "2013-07-02"] + [10 17 #t "2015-02-09"] + [8 60 #t "2013-10-27"] + [13 24 #t "2014-02-11"] + [2 71 #t "2014-11-25"] + [11 29 #t "2014-01-04"] + [13 91 #t "2015-11-27"] + [9 21 #t "2014-07-26"] + [11 99 #t "2015-09-04"] + [5 44 #t "2013-12-02"] + [8 51 #t "2015-04-10"] + [8 9 #t "2015-09-26"] + [11 19 #t "2014-12-01"] + [2 38 #t "2014-02-10"] + [10 12 #t "2014-07-14"] + [10 30 #t "2013-12-15"] + [8 33 #t "2013-03-16"] + [9 49 #t "2015-09-17"] + [6 38 #t "2013-05-19"] + [15 93 #t "2014-03-12"] + [2 36 #t "2013-01-27"] + [7 98 #t "2015-09-17"] + [4 6 #t "2015-09-18"] + [6 34 #t "2013-09-16"] + [11 73 #t "2014-02-20"] + [14 46 #t "2014-07-05"] + [1 44 #t "2014-10-18"] + [10 83 #t "2013-12-22"] + [3 21 #t "2014-11-05"] + [12 57 #t "2014-12-19"] + [10 77 #t "2015-07-25"] + [10 97 #t "2013-08-05"] + [14 8 #t "2015-04-16"] + [12 13 #t "2015-09-11"] + [15 81 #t "2015-10-29"] + [13 17 #t "2014-08-18"] + [15 2 #t "2014-08-25"] + [8 74 #t "2014-08-11"] + [7 90 #t "2013-02-13"] + [4 84 #t "2014-08-26"] + [10 87 #t "2014-01-09"] + [8 88 #t "2013-08-21"] + [6 85 #t "2015-12-26"] + [8 62 #t "2014-05-21"] + [4 97 #t "2015-02-15"] + [4 65 #t "2014-02-11"] + [9 1 #t "2015-10-08"] + [13 96 #t "2014-10-10"] + [10 83 #t "2015-11-22"] + [15 24 #t "2013-07-24"] + [10 38 #t "2014-09-14"] + [12 3 #t "2015-11-13"] + [4 78 #t "2014-10-13"] + [14 12 #t "2014-07-17"] + [3 18 #t "2014-05-31"] + [11 10 #t "2013-10-19"] + [11 81 #t "2013-03-13"] + [5 61 #t "2014-11-21"] + [13 81 #t "2014-12-17"] + [13 24 #t "2014-09-04"] + [8 54 #t "2013-02-21"] + [10 42 #t "2014-07-08"] + [7 97 #t "2014-09-23"] + [3 14 #t "2013-03-08"] + [12 58 #t "2014-01-31"] + [11 100 #t "2015-09-04"] + [8 90 #t "2014-03-06"] + [12 20 #t "2015-05-05"] + [3 47 #t "2014-06-30"] + [5 44 #t "2015-01-04"] + [1 47 #t "2013-09-10"] + [7 11 #t "2015-05-22"] + [15 87 #t "2013-12-29"] + [2 66 #t "2014-07-17"] + [2 52 #t "2014-07-09"] + [11 3 #t "2015-06-29"] + [9 12 #t "2013-09-16"] + [2 39 #t "2014-07-17"] + [11 3 #t "2014-03-13"] + [10 47 #t "2015-03-04"] + [15 78 #t "2015-08-21"] + [12 3 #t "2014-04-19"] + [11 45 #t "2014-11-07"] + [2 75 #t "2013-05-03"] + [10 9 #t "2013-03-08"] + [2 51 #t "2013-10-04"] + [4 4 #t "2014-07-10"] + [8 57 #t "2014-07-16"] + [8 38 #t "2014-08-15"] + [5 31 #t "2014-01-06"] + [3 54 #t "2015-05-27"] + [10 84 #t "2015-07-05"] + [5 54 #t "2013-03-01"] + [3 43 #t "2015-08-06"] + [8 70 #t "2014-11-09"] + [2 59 #t "2014-07-30"] + [11 18 #t "2015-01-14"] + [9 75 #t "2013-06-30"] + [13 41 #t "2015-08-01"] + [7 87 #t "2014-02-06"] + [6 91 #t "2013-03-19"] + [7 90 #t "2013-06-23"] + [8 61 #t "2014-04-11"] + [5 35 #t "2014-10-28"] + [2 82 #t "2014-06-09"] + [9 35 #t "2013-10-23"] + [6 52 #t "2014-09-28"] + [6 96 #t "2015-09-04"] + [11 59 #t "2015-09-18"] + [12 34 #t "2015-08-09"] + [10 80 #t "2015-04-08"] + [4 78 #t "2015-03-01"] + [6 4 #t "2015-09-01"] + [4 35 #t "2014-07-07"] + [13 50 #t "2013-06-23"] + [11 69 #t "2014-03-17"] + [10 62 #t "2013-03-15"] + [13 31 #t "2015-03-27"] + [13 42 #t "2014-10-02"] + [14 86 #t "2013-05-14"] + [4 83 #t "2014-08-13"] + [9 21 #t "2015-04-18"] + [14 90 #t "2014-06-15"] + [12 65 #t "2015-05-05"] + [7 57 #t "2013-03-08"] + [12 70 #t "2014-09-06"] + [11 16 #t "2014-02-09"] + [7 38 #t "2013-10-12"] + [2 40 #t "2014-03-09"] + [8 52 #t "2015-06-22"] + [7 84 #t "2013-01-22"] + [9 4 #t "2014-08-20"] + [2 4 #t "2014-03-04"] + [8 56 #t "2014-02-03"] + [6 23 #t "2013-10-29"] + [7 87 #t "2013-10-02"] + [5 28 #t "2014-02-14"] + [5 19 #t "2013-09-08"] + [2 13 #t "2014-06-30"] + [12 65 #t "2013-07-25"] + [4 73 #t "2015-11-06"] + [7 56 #t "2013-07-14"] + [1 46 #t "2014-03-09"] + [13 58 #t "2013-07-10"] + [4 68 #t "2013-04-12"] + [14 86 #t "2014-03-09"] + [7 89 #t "2014-11-22"] + [4 42 #t "2014-04-13"] + [13 83 #t "2014-10-19"] + [10 66 #t "2014-07-07"] + [11 69 #t "2013-08-19"] + [2 18 #t "2014-11-28"] + [12 7 #t "2015-08-16"] + [7 45 #t "2014-03-20"] + [8 85 #t "2014-10-09"] + [13 27 #t "2014-05-16"] + [8 6 #t "2014-08-24"] + [9 52 #t "2013-04-11"] + [2 75 #t "2015-02-26"] + [11 65 #t "2014-05-29"] + [7 30 #t "2013-06-03"] + [11 14 #t "2013-06-26"] + [11 61 #t "2014-11-09"] + [8 81 #t "2013-06-27"] + [10 92 #t "2014-05-07"] + [3 52 #t "2014-01-26"] + [5 56 #t "2014-11-14"] + [11 75 #t "2014-04-02"] + [9 13 #t "2014-10-22"] + [4 25 #t "2015-05-18"] + [10 70 #t "2015-05-04"] + [2 48 #t "2014-06-21"] + [6 78 #t "2014-03-28"] + [12 68 #t "2014-10-24"] + [10 8 #t "2014-02-26"] + [5 63 #t "2015-10-12"] + [14 66 #t "2015-08-06"] + [2 3 #t "2014-05-14"] + [3 36 #t "2014-04-27"] + [11 71 #t "2015-04-24"] + [1 85 #t "2015-03-02"] + [13 68 #t "2015-06-22"] + [5 34 #t "2014-09-25"] + [2 75 #t "2014-07-29"] + [7 13 #t "2014-10-03"] + [12 86 #t "2014-01-10"] + [13 100 #t "2015-07-17"] + [8 59 #t "2014-10-15"] + [15 27 #t "2014-05-17"] + [13 83 #t "2013-10-11"] + [2 68 #t "2014-06-03"] + [10 23 #t "2013-04-08"] + [12 17 #t "2013-01-10"] + [8 89 #t "2014-05-01"] + [4 11 #t "2013-06-12"] + [3 97 #t "2015-05-22"] + [14 77 #t "2013-10-19"] + [10 69 #t "2014-10-10"] + [13 79 #t "2014-11-11"] + [5 95 #t "2014-07-22"] + [14 33 #t "2015-08-26"] + [2 75 #t "2014-07-10"] + [7 97 #t "2014-03-01"] + [6 88 #t "2014-08-04"] + [12 73 #t "2013-11-16"] + [14 61 #t "2013-05-21"] + [9 43 #t "2015-03-07"] + [4 44 #t "2013-08-20"] + [15 61 #t "2015-07-18"] + [11 98 #t "2014-09-29"] + [11 32 #t "2013-08-02"] + [3 94 #t "2014-12-06"] + [4 48 #t "2015-08-20"] + [11 59 #t "2014-07-21"] + [9 77 #t "2014-06-05"] + [7 10 #t "2015-04-23"] + [7 17 #t "2013-04-01"] + [9 45 #t "2015-02-13"] + [3 2 #t "2014-12-23"] + [9 85 #t "2014-12-11"] + [6 22 #t "2015-04-24"] + [12 96 #t "2013-06-11"] + [1 78 #t "2014-06-09"] + [13 29 #t "2014-02-10"] + [3 54 #t "2013-01-19"] + [11 60 #t "2014-08-30"] + [2 16 #t "2013-11-27"] + [9 41 #t "2014-05-14"] + [10 98 #t "2014-01-26"] + [13 98 #t "2015-07-01"] + [12 4 #t "2015-10-04"] + [1 63 #t "2014-03-14"] + [11 2 #t "2015-10-23"] + [14 64 #t "2014-05-27"] + [6 42 #t "2014-05-01"] + [2 44 #t "2014-09-26"] + [5 42 #t "2013-08-15"] + [5 39 #t "2013-04-26"] + [11 94 #t "2014-07-14"] + [4 17 #t "2015-08-18"] + [9 3 #t "2014-02-19"] + [3 75 #t "2014-05-18"] + [10 38 #t "2015-09-22"] + [10 74 #t "2013-03-28"] + [11 25 #t "2014-01-04"] + [7 20 #t "2014-09-14"] + [7 33 #t "2014-05-18"] + [2 40 #t "2013-02-19"] + [9 62 #t "2013-06-16"] + [6 5 #t "2014-11-25"] + [14 13 #t "2014-07-19"] + [4 82 #t "2013-04-24"] + [6 86 #t "2014-04-10"] + [15 66 #t "2013-05-31"] + [10 63 #t "2014-09-03"] + [13 46 #t "2014-06-25"] + [13 44 #t "2015-02-24"] + [5 82 #t "2014-06-19"] + [12 57 #t "2014-09-12"] + [5 96 #t "2015-03-16"] + [12 36 #t "2015-05-11"] + [6 100 #t "2015-11-28"] + [9 44 #t "2014-10-24"] + [13 70 #t "2014-04-03"] + [10 77 #t "2014-04-12"] + [13 42 #t "2015-07-23"] + [6 99 #t "2013-06-02"] + [9 22 #t "2015-07-06"] + [13 83 #t "2014-12-16"] + [13 27 #t "2014-07-20"] + [10 94 #t "2015-11-05"] + [13 70 #t "2015-01-23"] + [5 59 #t "2014-05-20"] + [12 61 #t "2013-01-25"] + [1 5 #t "2015-07-23"] + [5 95 #t "2013-08-19"] + [6 88 #t "2014-07-25"] + [3 54 #t "2013-11-07"] + [11 57 #t "2015-07-20"] + [7 27 #t "2014-08-26"] + [2 62 #t "2015-05-03"] + [4 36 #t "2014-04-14"] + [12 1 #t "2013-11-09"] + [4 27 #t "2013-07-14"] + [10 63 #t "2013-11-15"] + [6 31 #t "2014-05-22"] + [2 90 #t "2015-10-04"] + [8 2 #t "2013-12-03"] + [1 86 #t "2015-09-23"] + [7 46 #t "2014-09-05"] + [9 8 #t "2015-07-29"] + [1 51 #t "2014-05-30"] + [7 68 #t "2014-03-25"] + [14 74 #t "2015-02-23"] + [8 63 #t "2015-05-12"] + [1 68 #t "2013-04-11"] + [7 20 #t "2015-03-28"] + [14 28 #t "2014-07-24"] + [13 8 #t "2014-02-03"] + [3 40 #t "2013-09-27"] + [9 72 #t "2014-01-19"] + [11 15 #t "2013-05-14"] + [9 11 #t "2014-03-30"] + [4 10 #t "2014-12-03"] + [4 79 #t "2014-11-07"] + [4 74 #t "2013-05-09"] + [5 78 #t "2015-12-08"] + [12 74 #t "2015-06-04"] + [6 89 #t "2014-06-14"] + [2 87 #t "2013-01-23"] + [15 84 #t "2015-07-18"] + [4 65 #t "2015-03-11"] + [7 66 #t "2013-07-25"] + [10 14 #t "2013-11-29"] + [5 77 #t "2014-06-02"] + [8 74 #t "2013-04-30"] + [14 7 #t "2014-03-05"] + [4 45 #t "2013-11-05"] + [15 96 #t "2013-11-26"] + [4 45 #t "2015-01-15"] + [15 52 #t "2015-05-01"] + [6 46 #t "2014-02-25"] + [12 42 #t "2014-11-10"] + [13 17 #t "2014-05-20"] + [6 44 #t "2015-03-27"] + [3 71 #t "2014-04-14"] + [2 35 #t "2013-10-01"] + [9 74 #t "2015-03-03"] + [4 68 #t "2014-12-01"] + [6 40 #t "2013-11-25"] + [4 63 #t "2014-06-14"] + [11 12 #t "2013-08-05"] + [13 41 #t "2015-02-03"] + [11 13 #t "2014-10-05"] + [10 18 #t "2015-03-20"] + [5 20 #t "2014-05-11"] + [5 79 #t "2014-02-22"] + [7 15 #t "2013-04-15"] + [6 25 #t "2014-03-28"] + [14 9 #t "2014-08-12"] + [8 53 #t "2014-04-24"] + [9 78 #t "2014-07-02"] + [3 4 #t "2014-06-26"] + [7 3 #t "2015-10-29"] + [6 56 #t "2015-10-28"] + [4 65 #t "2014-08-12"] + [15 35 #t "2014-07-28"] + [8 49 #t "2014-09-01"] + [11 80 #t "2014-07-31"] + [10 51 #t "2015-03-01"] + [14 70 #t "2013-07-15"] + [12 18 #t "2013-10-06"] + [8 80 #t "2013-10-31"] + [15 91 #t "2013-11-16"] + [9 78 #t "2014-09-06"] + [9 88 #t "2013-06-04"] + [12 88 #t "2014-05-29"] + [7 22 #t "2013-06-07"] + [2 38 #t "2014-06-21"] + [4 7 #t "2014-05-07"] + [2 49 #t "2013-04-18"] + [13 56 #t "2014-07-19"] + [9 66 #t "2013-06-14"] + [9 57 #t "2014-07-29"] + [5 91 #t "2014-04-04"] + [10 46 #t "2015-06-08"] + [10 97 #t "2014-08-01"] + [2 53 #t "2014-07-04"] + [14 54 #t "2015-07-27"] + [2 81 #t "2013-08-17"] + [11 77 #t "2015-07-12"] + [13 39 #t "2013-08-03"] + [7 86 #t "2014-01-16"] + [14 68 #t "2014-05-07"] + [13 61 #t "2014-05-29"] + [6 90 #t "2015-09-16"] + [11 59 #t "2014-10-13"] + [11 41 #t "2015-11-07"] + [12 2 #t "2015-04-23"] + [10 76 #t "2013-10-18"] + [14 77 #t "2014-02-04"] + [2 80 #t "2014-01-04"] + [3 65 #t "2015-08-15"] + [9 59 #t "2013-04-03"] + [11 6 #t "2015-11-10"] + [9 29 #t "2013-06-30"] + [10 37 #t "2014-06-27"] + [2 26 #t "2013-12-11"] + [3 28 #t "2013-03-05"] + [2 94 #t "2015-03-13"] + [11 72 #t "2015-10-05"] + [7 39 #t "2014-08-15"] + [9 6 #t "2014-05-18"] + [10 98 #t "2013-04-26"] + [9 92 #t "2014-02-18"] + [13 39 #t "2014-08-09"] + [9 21 #t "2014-08-12"] + [2 60 #t "2014-01-27"] + [6 58 #t "2013-07-22"] + [8 41 #t "2014-05-16"] + [6 15 #t "2014-08-30"] + [12 7 #t "2015-04-21"] + [7 14 #t "2014-09-01"] + [10 43 #t "2014-11-27"] + [14 22 #t "2014-05-22"] + [2 48 #t "2015-11-22"] + [9 92 #t "2014-02-12"] + [8 48 #t "2015-10-26"] + [13 97 #t "2015-05-09"] + [6 12 #t "2014-07-14"] + [11 22 #t "2013-06-17"] + [4 23 #t "2013-03-23"] + [10 68 #t "2014-06-22"] + [5 78 #t "2014-07-16"] + [4 32 #t "2015-03-01"] + [10 33 #t "2014-05-23"] + [10 6 #t "2013-07-07"] + [7 98 #t "2015-04-04"] + [14 29 #t "2015-02-03"] + [2 53 #t "2014-09-08"] + [9 43 #t "2014-07-30"] + [14 74 #t "2015-11-01"] + [3 94 #t "2014-09-27"] + [11 86 #t "2015-09-27"] + [1 50 #t "2014-11-07"] + [4 43 #t "2013-06-19"] + [2 85 #t "2015-07-28"] + [5 24 #t "2014-11-09"] + [14 88 #t "2014-08-26"] + [6 61 #t "2014-08-09"] + [2 83 #t "2015-12-19"] + [1 38 #t "2015-07-25"] + [6 49 #t "2015-01-25"] + [12 31 #t "2015-02-09"] + [6 61 #t "2014-11-28"] + [5 50 #t "2013-06-12"] + [7 81 #t "2014-11-03"] + [9 48 #t "2014-03-27"] + [6 72 #t "2014-09-24"] + [4 59 #t "2013-10-06"] + [8 48 #t "2014-04-18"] + [11 88 #t "2015-04-10"] + [10 67 #t "2014-02-28"] + [2 74 #t "2014-01-18"] + [10 70 #t "2014-12-07"] + [4 53 #t "2014-11-07"] + [8 81 #t "2015-02-18"] + [3 72 #t "2014-05-05"] + [15 72 #t "2014-06-17"] + [4 8 #t "2015-06-13"] + [8 73 #t "2014-11-30"] + [8 93 #t "2014-09-20"] + [14 44 #t "2014-01-21"] + [8 68 #t "2014-06-05"] + [5 94 #t "2013-05-20"] + [3 7 #t "2015-05-29"] + [7 49 #t "2013-09-10"] + [7 49 #t "2013-07-26"] + [15 74 #t "2015-10-26"] + [7 66 #t "2015-07-29"] + [8 93 #t "2015-07-07"] + [13 79 #t "2014-11-12"] + [6 7 #t "2014-12-27"] + [3 80 #t "2015-06-22"] + [13 6 #t "2014-09-09"] + [3 82 #t "2015-06-27"] + [12 13 #t "2013-06-29"] + [14 86 #t "2014-01-07"] + [5 66 #t "2014-05-26"] + [14 62 #t "2013-08-18"] + [10 97 #t "2013-11-19"] + [6 94 #t "2013-04-19"] + [2 41 #t "2014-03-03"] + [13 74 #t "2014-05-26"] + [7 63 #t "2014-05-28"] + [14 31 #t "2013-12-04"] + [13 41 #t "2013-06-15"] + [12 51 #t "2015-12-26"] + [4 65 #t "2015-12-18"] + [5 64 #t "2013-08-02"] + [12 18 #t "2013-10-24"] + [4 38 #t "2014-04-26"] + [7 30 #t "2014-09-18"] + [5 17 #t "2014-05-18"] + [2 76 #t "2015-09-04"] + [13 42 #t "2015-05-26"] + [9 74 #t "2014-08-01"] + [7 42 #t "2013-06-21"] + [3 26 #t "2015-09-28"] + [4 27 #t "2013-05-14"] + [12 21 #t "2013-05-26"] + [13 20 #t "2015-07-15"] + [2 85 #t "2014-05-02"] + [7 52 #t "2014-10-21"] + [5 3 #t "2014-05-04"] + [5 79 #t "2014-07-11"] + [3 10 #t "2014-05-31"] + [9 2 #t "2015-01-28"] + [3 85 #t "2013-11-13"] + [5 40 #t "2015-09-11"] + [11 70 #t "2015-09-20"] + [5 86 #t "2014-12-05"] + [3 86 #t "2014-04-24"] + [5 52 #t "2014-11-05"] + [9 72 #t "2013-11-22"] + [8 27 #t "2015-09-28"] + [8 48 #t "2014-08-02"] + [1 35 #t "2014-05-26"] + [11 6 #t "2014-10-16"] + [1 58 #t "2013-11-18"] + [8 90 #t "2014-08-03"] + [5 47 #t "2013-09-02"] + [11 88 #t "2013-12-11"] + [3 71 #t "2014-09-26"] + [14 66 #t "2015-06-13"] + [6 27 #t "2015-08-16"] + [4 42 #t "2015-01-30"] + [10 67 #t "2014-12-09"] + [3 75 #t "2015-10-08"] + [9 68 #t "2013-11-09"] + [9 87 #t "2014-11-08"] + [5 12 #t "2014-02-05"] + [13 87 #t "2013-04-23"] + [3 72 #t "2015-05-25"] + [3 95 #t "2015-12-18"] + [4 43 #t "2013-04-14"] + [6 17 #t "2014-06-28"] + [12 32 #t "2014-01-05"] + [14 96 #t "2013-04-13"] + [1 76 #t "2015-10-29"] + [5 93 #t "2014-08-21"] + [14 53 #t "2013-11-18"] + [14 20 #t "2014-10-25"] + [3 91 #t "2015-10-19"] + [8 8 #t "2015-11-21"] + [13 34 #t "2013-08-20"] + [2 54 #t "2014-05-08"] + [3 66 #t "2014-10-16"] + [3 57 #t "2014-09-16"] + [10 12 #t "2015-04-12"] + [10 93 #t "2014-01-04"] + [6 20 #t "2014-03-18"] + [14 50 #t "2015-08-20"] + [7 35 #t "2014-07-24"] + [9 25 #t "2014-07-08"] + [13 43 #t "2014-12-23"] + [3 43 #t "2014-10-06"] + [3 58 #t "2014-06-10"] + [8 59 #t "2013-07-05"] + [8 9 #t "2014-03-02"] + [12 11 #t "2013-11-12"] + [8 82 #t "2014-12-19"] + [3 88 #t "2014-03-23"] + [10 81 #t "2015-07-01"] + [4 31 #t "2014-05-01"] + [1 10 #t "2013-03-12"] + [7 98 #t "2015-04-21"] + [10 69 #t "2013-05-03"] + [4 7 #t "2014-11-09"] + [11 57 #t "2014-06-05"] + [4 75 #t "2013-08-20"] + [10 8 #t "2014-10-06"] + [9 48 #t "2015-10-06"] + [14 38 #t "2013-04-14"] + [6 41 #t "2014-10-25"] + [5 14 #t "2013-05-07"] + [11 38 #t "2015-05-13"] + [3 33 #t "2014-11-08"] + [1 72 #t "2013-07-25"] + [10 84 #t "2013-04-07"] + [10 24 #t "2014-06-25"] + [3 50 #t "2013-02-06"] + [14 18 #t "2015-10-28"] + [7 95 #t "2014-10-15"] + [13 86 #t "2014-05-05"] + [14 72 #t "2015-08-05"] + [13 24 #t "2015-10-22"] + [10 19 #t "2014-07-06"] + [1 26 #t "2014-12-31"] + [9 12 #t "2014-06-29"] + [8 32 #t "2013-08-04"] + [3 28 #t "2015-09-19"] + [15 37 #t "2014-10-23"] + [8 8 #t "2014-09-16"] + [7 100 #t "2014-01-19"] + [8 85 #t "2014-03-31"] + [8 23 #t "2014-02-18"] + [4 95 #t "2015-03-03"] + [11 93 #t "2013-10-28"] + [6 75 #t "2014-07-25"] + [10 18 #t "2013-08-27"] + [14 68 #t "2013-02-20"] + [12 13 #t "2015-02-14"] + [4 2 #t "2013-02-27"] + [7 81 #t "2013-04-16"] + [3 21 #t "2013-04-07"] + [6 43 #t "2014-09-30"] + [5 73 #t "2014-11-29"] + [2 38 #t "2014-08-09"] + [14 60 #t "2014-04-29"] + [10 90 #t "2015-12-29"] + [7 3 #t "2015-06-27"] + [2 18 #t "2014-10-14"] + [4 95 #t "2013-05-27"] + [4 65 #t "2014-06-24"] + [10 32 #t "2014-08-02"] + [13 72 #t "2013-02-22"] + [4 9 #t "2014-02-07"] + [12 49 #t "2014-11-18"] + [11 99 #t "2014-06-29"] + [10 30 #t "2014-04-21"] + [12 5 #t "2014-03-26"] + [7 56 #t "2014-01-04"] + [9 16 #t "2013-10-11"] + [6 44 #t "2013-11-11"] + [2 27 #t "2015-03-18"] + [12 25 #t "2014-11-08"] + [1 7 #t "2015-05-29"] + [7 91 #t "2015-06-18"] + [6 89 #t "2015-11-16"] + [8 12 #t "2013-10-01"] + [5 9 #t "2013-04-18"] + [3 81 #t "2014-05-01"] + [7 53 #t "2013-03-26"] + [6 45 #t "2014-02-13"] + [8 84 #t "2015-04-20"] + [5 2 #t "2013-10-02"] + [8 7 #t "2014-09-10"] + [15 41 #t "2013-07-19"] + [13 18 #t "2014-07-24"] + [14 54 #t "2015-09-18"] + [11 84 #t "2014-08-13"] + [7 56 #t "2014-03-29"] + [13 37 #t "2014-05-21"] + [4 96 #t "2014-04-30"] + [6 76 #t "2014-09-16"] + [5 21 #t "2014-07-08"] + [8 61 #t "2014-03-10"] + [5 26 #t "2014-09-05"] + [8 100 #t "2013-05-29"] + [3 47 #t "2014-05-08"] + [7 46 #t "2015-10-04"] + [5 73 #t "2014-02-10"] + [1 54 #t "2014-02-08"] + [12 46 #t "2014-06-29"] + [14 46 #t "2014-10-16"] + [10 69 #t "2015-10-29"] + [1 39 #t "2013-06-03"] + [3 23 #t "2014-03-09"] + [10 43 #t "2014-07-13"] + [14 95 #t "2014-04-17"] + [10 75 #t "2014-03-17"] + [4 50 #t "2013-02-18"] + [12 43 #t "2013-11-01"] + [9 33 #t "2015-07-02"] + [4 91 #t "2013-04-02"] + [15 16 #t "2014-04-12"] + [3 42 #t "2014-02-10"] + [12 65 #t "2014-03-20"] + [13 72 #t "2015-07-22"] + [13 86 #t "2015-05-01"] + [13 93 #t "2013-03-19"] + [10 49 #t "2013-12-19"] + [13 8 #t "2014-12-05"] + [15 52 #t "2015-08-09"] + [7 95 #t "2013-12-11"] + [9 90 #t "2014-10-10"] + [8 50 #t "2015-03-05"] + [6 11 #t "2014-01-12"] + [13 26 #t "2014-08-25"] + [3 39 #t "2014-10-14"] + [8 36 #t "2015-11-13"] + [5 97 #t "2014-05-20"] + [10 35 #t "2014-05-07"] + [11 74 #t "2015-04-06"] + [15 75 #t "2013-04-28"] + [2 88 #t "2014-01-18"] + [9 58 #t "2014-04-16"] + [6 41 #t "2014-11-05"] + [10 44 #t "2015-04-11"] + [10 64 #t "2013-07-20"] + [10 19 #t "2014-02-12"] + [4 13 #t "2014-03-01"] + [13 27 #t "2014-04-02"] + [15 33 #t "2013-03-28"] + [3 6 #t "2015-09-05"] + [7 63 #t "2014-03-08"] + [12 94 #t "2014-09-23"] + [7 38 #t "2014-04-03"] + [11 85 #t "2014-02-17"] + [9 76 #t "2014-07-13"] + [8 83 #t "2014-05-28"] + [14 42 #t "2015-02-03"] + [4 35 #t "2014-03-25"] + [7 58 #t "2014-03-25"] + [3 54 #t "2014-02-25"] + [5 60 #t "2014-12-16"] + [9 100 #t "2014-05-20"] + [12 6 #t "2014-04-09"] + [3 76 #t "2013-07-29"] + [8 73 #t "2013-04-26"] + [13 33 #t "2014-11-03"] + [6 45 #t "2014-05-17"] + [5 87 #t "2014-10-07"] + [5 90 #t "2015-07-21"] + [9 36 #t "2015-08-26"] + [7 57 #t "2015-05-21"] + [9 20 #t "2013-10-03"] + [4 13 #t "2013-05-18"] + [13 63 #t "2014-03-22"] + [5 42 #t "2015-08-22"] + [9 49 #t "2015-03-02"] + [6 15 #t "2014-10-02"] + [7 17 #t "2013-07-18"] + [11 63 #t "2014-02-17"] + [3 90 #t "2013-02-26"] + [13 90 #t "2013-06-08"] + [6 46 #t "2014-03-24"] + [15 91 #t "2014-02-19"] + [10 65 #t "2014-10-10"] + [3 76 #t "2014-05-08"] + [13 43 #t "2014-02-11"] + [7 15 #t "2015-07-09"] + [1 36 #t "2014-03-03"] + [9 59 #t "2014-08-03"] + [5 86 #t "2015-04-02"] + [14 63 #t "2014-07-11"] + [5 94 #t "2013-11-24"] + [14 54 #t "2014-08-03"] + [2 37 #t "2014-08-02"] + [4 62 #t "2015-11-08"] + [7 17 #t "2013-10-01"] + [13 59 #t "2014-01-03"] + [4 22 #t "2013-03-14"] + [3 94 #t "2015-12-16"] + [14 89 #t "2014-06-06"] + [15 23 #t "2015-08-19"] + [8 12 #t "2015-03-17"] + [8 93 #t "2015-05-29"] + [3 20 #t "2013-05-20"] + [9 2 #t "2013-04-03"] + [13 73 #t "2014-06-30"] + [10 5 #t "2015-05-04"] + [4 98 #t "2014-08-29"] + [5 38 #t "2015-04-15"] + [3 41 #t "2014-10-19"] + [2 53 #t "2013-06-21"] + [12 97 #t "2015-11-03"] + [15 68 #t "2013-05-06"] + [15 22 #t "2013-08-16"] + [11 57 #t "2013-05-01"] + [6 91 #t "2015-02-22"] + [2 63 #t "2014-04-22"] + [13 70 #t "2013-03-06"] + [11 86 #t "2013-11-02"] + [13 23 #t "2015-05-26"] + [12 5 #t "2013-11-17"] + [5 43 #t "2015-12-02"] + [11 96 #t "2014-03-26"] + [2 90 #t "2013-12-03"] + [10 21 #t "2014-08-24"] + [11 20 #t "2014-10-28"] + [13 44 #t "2015-09-11"] + [11 57 #t "2014-08-02"] + [6 45 #t "2014-09-29"] + [13 68 #t "2014-08-03"] + [9 75 #t "2015-07-24"] + [7 37 #t "2014-10-06"] + [6 21 #t "2014-11-06"] + [14 49 #t "2013-11-22"] + [7 67 #t "2014-08-03"] + [4 75 #t "2014-10-20"] + [13 13 #t "2015-08-26"] + [2 58 #t "2014-09-01"] + [2 23 #t "2013-03-19"] + [4 38 #t "2014-10-03"] + [7 26 #t "2015-10-06"] + [2 93 #t "2014-10-23"] + [2 41 #t "2014-07-02"] + [7 99 #t "2014-10-18"] + [14 64 #t "2014-09-10"] + [10 9 #t "2014-10-25"] + [6 48 #t "2014-12-25"] + [8 58 #t "2014-02-18"] + [3 35 #t "2014-08-25"] + [6 98 #t "2014-07-01"] + [8 97 #t "2013-09-16"] + [13 26 #t "2014-09-22"] + [2 91 #t "2014-04-15"] + [6 20 #t "2015-06-30"] + [15 74 #t "2014-06-13"] + [7 62 #t "2014-10-13"] + [7 95 #t "2014-06-03"] + [1 96 #t "2014-10-16"] + [9 84 #t "2013-10-20"] + [4 55 #t "2014-09-24"] + [13 86 #t "2014-02-16"] + [14 9 #t "2015-02-05"] + [9 37 #t "2014-06-19"] + [3 12 #t "2015-11-13"] + [10 91 #t "2014-06-10"] + [1 13 #t "2013-10-29"] + [4 57 #t "2013-05-10"] + [5 57 #t "2014-05-28"] + [14 31 #t "2013-06-18"] + [3 29 #t "2014-06-16"] + [7 93 #t "2015-06-07"] + [7 87 #t "2015-11-21"] + [9 53 #t "2015-09-26"] + [14 93 #t "2014-10-20"] + [14 37 #t "2014-08-14"] + [3 30 #t "2013-03-21"] + [10 82 #t "2013-06-05"] + [4 40 #t "2015-07-17"] + [8 45 #t "2014-09-08"] + [6 84 #t "2013-02-15"] + [11 87 #t "2015-11-06"] + [10 93 #t "2014-12-24"] + [2 54 #t "2014-08-02"] + [3 34 #t "2014-05-07"] + [13 48 #t "2014-10-01"] + [4 48 #t "2014-10-24"] + [1 46 #t "2015-04-25"] + [14 85 #t "2015-03-15"] + [4 37 #t "2014-03-05"] + [6 62 #t "2014-02-20"] + [2 73 #t "2014-08-20"] + [2 14 #t "2013-09-29"] + [6 83 #t "2013-09-01"] + [11 89 #t "2013-10-16"] + [3 58 #t "2013-12-04"] + [3 36 #t "2014-06-22"] + [5 96 #t "2015-06-26"] + [5 18 #t "2014-04-22"] + [4 54 #t "2014-10-29"] + [9 31 #t "2013-09-29"] + [12 49 #t "2015-04-19"] + [3 38 #t "2013-01-26"] + [4 88 #t "2013-01-03"] + [12 58 #t "2015-11-25"] + [12 58 #t "2015-08-24"] + [15 3 #t "2015-05-22"] + [10 17 #t "2013-05-04"] + [6 85 #t "2013-08-10"] + [7 18 #t "2015-07-09"] + [12 67 #t "2015-06-15"] + [8 96 #t "2015-02-22"] + [15 88 #t "2015-02-13"] + [8 70 #t "2015-12-22"] + [8 48 #t "2014-10-04"] + [3 91 #t "2013-06-05"] + [8 83 #t "2014-11-06"] + [12 5 #t "2013-11-28"] + [13 88 #t "2014-03-29"] + [2 73 #t "2014-11-02"] + [7 13 #t "2013-10-22"] + [13 17 #t "2015-06-16"] + [7 11 #t "2014-03-09"] + [2 84 #t "2014-03-06"] + [8 79 #t "2014-06-13"] + [2 77 #t "2014-04-10"] + [3 40 #t "2014-05-11"] + [8 30 #t "2013-03-06"] + [1 47 #t "2014-12-07"] + [11 49 #t "2014-12-21"] + [5 39 #t "2014-10-31"] + [3 98 #t "2014-10-22"] + [9 20 #t "2015-04-09"] + [13 66 #t "2013-07-23"] + [15 18 #t "2013-04-26"] + [9 37 #t "2013-02-06"] + [12 79 #t "2014-09-07"] + [8 49 #t "2014-04-26"] + [6 87 #t "2015-07-01"] + [2 70 #t "2015-09-27"] + [7 44 #t "2014-11-05"] + [6 65 #t "2014-11-27"] + [8 51 #t "2015-09-07"] + [6 11 #t "2015-08-21"] + [11 76 #t "2014-05-21"] + [5 94 #t "2014-09-20"] + [1 97 #t "2015-04-05"] + [2 20 #t "2014-11-21"] + [9 25 #t "2014-06-03"] + [4 10 #t "2013-09-21"] + [14 78 #t "2013-09-14"] + [6 34 #t "2014-05-30"] + [1 16 #t "2014-03-30"] + [15 36 #t "2014-09-23"] + [8 5 #t "2013-08-21"] + [11 39 #t "2014-10-10"] + [4 66 #t "2014-03-16"] + [12 74 #t "2014-10-07"] + [6 76 #t "2015-08-09"] + [14 62 #t "2015-07-22"] + [14 98 #t "2015-08-13"] + [8 40 #t "2014-04-03"] + [3 33 #t "2014-11-13"] + [12 42 #t "2014-05-09"] + [8 77 #t "2015-09-24"] + [2 16 #t "2014-12-09"] + [4 29 #t "2015-05-29"] + [11 49 #t "2014-03-05"] + [13 58 #t "2014-04-29"] + [9 34 #t "2014-05-04"] + [12 5 #t "2015-04-16"] + [7 67 #t "2015-02-07"] + [2 92 #t "2014-06-03"]]]] diff --git a/test/metabase/test/data/impl.clj b/test/metabase/test/data/impl.clj index 9fe9a419f67..3c9147adeda 100644 --- a/test/metabase/test/data/impl.clj +++ b/test/metabase/test/data/impl.clj @@ -16,7 +16,6 @@ [interface :as tx]] [metabase.test.initialize :as initialize] [metabase.test.util.timezone :as tu.tz] - [metabase.util.date :as du] [toucan.db :as db])) ;;; +----------------------------------------------------------------------------------------------------------------+ @@ -72,11 +71,11 @@ (def ^:private create-database-timeout-ms "Max amount of time to wait for driver text extensions to create a DB and load test data." - (du/minutes->ms 4)) ; 4 minutes + (u/minutes->ms 4)) ; 4 minutes (def ^:private sync-timeout-ms "Max amount of time to wait for sync to complete." - (du/minutes->ms 5)) ; five minutes + (u/minutes->ms 5)) ; five minutes (defn- create-database! [driver {:keys [database-name], :as database-definition}] {:pre [(seq database-name)]} @@ -84,7 +83,7 @@ ;; Create the database and load its data ;; ALWAYS CREATE DATABASE AND LOAD DATA AS UTC! Unless you like broken tests (u/with-timeout create-database-timeout-ms - (tu.tz/with-jvm-tz "UTC" + (tu.tz/with-system-timezone-id "UTC" (tx/create-db! driver database-definition))) ;; Add DB object to Metabase DB (let [db (db/insert! Database @@ -93,7 +92,7 @@ :details (tx/dbdef->connection-details driver :db database-definition))] ;; sync newly added DB (u/with-timeout sync-timeout-ms - (du/profile (format "Sync %s Database %s" driver database-name) + (u/profile (format "Sync %s Database %s" driver database-name) (sync/sync-database! db) ;; add extra metadata for fields (try diff --git a/test/metabase/test/data/interface.clj b/test/metabase/test/data/interface.clj index 08cb7c2d39d..22b4fb7765f 100644 --- a/test/metabase/test/data/interface.clj +++ b/test/metabase/test/data/interface.clj @@ -22,7 +22,7 @@ [metabase.query-processor.store :as qp.store] [metabase.test.initialize :as initialize] [metabase.util - [date :as du] + [date-2 :as u.date] [schema :as su]] [potemkin.types :as p.types] [pretty.core :as pretty] @@ -65,6 +65,7 @@ :table-definitions [ValidTableDefinition]} (partial instance? DatabaseDefinition))) +;; TODO - this should probably be a protocol instead (defmulti ^DatabaseDefinition get-dataset-definition "Return a definition of a dataset, so a test database can be created from it." {:arglists '([this])} @@ -130,7 +131,7 @@ (when-not (contains? @has-loaded-extensions driver) (locking has-loaded-extensions (when-not (contains? @has-loaded-extensions driver) - (du/profile (format "Load %s test extensions" driver) + (u/profile (format "Load %s test extensions" driver) (require-driver-test-extensions-ns driver) ;; if it doesn't have test extensions yet, it may be because it's relying on a parent driver to add them (e.g. ;; Redshift uses Postgres' test extensions). Load parents as appropriate and try again @@ -465,7 +466,9 @@ directory. (Filename should be `dataset-name` + `.edn`.)" [dataset-name :- su/NonBlankString] (let [get-def (delay - (let [file-contents (edn/read-string (slurp (str edn-definitions-dir dataset-name ".edn")))] + (let [file-contents (edn/read-string + {:eof nil, :readers {'t #'u.date/parse}} + (slurp (str edn-definitions-dir dataset-name ".edn")))] (apply dataset-definition dataset-name file-contents)))] (EDNDatasetDefinition. dataset-name get-def))) diff --git a/test/metabase/test/data/sql_jdbc/load_data.clj b/test/metabase/test/data/sql_jdbc/load_data.clj index 64bdb538ad2..8f62b9fa9a3 100644 --- a/test/metabase/test/data/sql_jdbc/load_data.clj +++ b/test/metabase/test/data/sql_jdbc/load_data.clj @@ -16,9 +16,7 @@ [execute :as execute] [spec :as spec]] [metabase.test.data.sql.ddl :as ddl] - [metabase.util - [date :as du] - [honeysql-extensions :as hx]]) + [metabase.util.honeysql-extensions :as hx]) (:import java.sql.SQLException)) (defmulti load-data! @@ -104,11 +102,7 @@ (:field-definitions tabledef))] ;; TIMEZONE FIXME (for [row (:rows tabledef)] - (zipmap fields-for-insert (for [v row] - (if (and (not (instance? java.sql.Time v)) - (instance? java.util.Date v)) - (du/->Timestamp v du/utc) - v)))))) + (zipmap fields-for-insert row)))) (defn- make-insert! "Used by `make-load-data-fn`; creates the actual `insert!` function that gets passed to the `insert-middleware-fns` @@ -211,5 +205,5 @@ (execute/execute-sql! driver :db dbdef (str/join ";\n" statements))) ;; Now load the data for each Table (doseq [tabledef table-definitions] - (du/profile (format "load-data for %s %s %s" (name driver) (:database-name dbdef) (:table-name tabledef)) + (u/profile (format "load-data for %s %s %s" (name driver) (:database-name dbdef) (:table-name tabledef)) (load-data! driver dbdef tabledef)))) diff --git a/test/metabase/test/initialize.clj b/test/metabase/test/initialize.clj index 1f9aef0f17b..8bd75ffcf84 100644 --- a/test/metabase/test/initialize.clj +++ b/test/metabase/test/initialize.clj @@ -2,8 +2,27 @@ "Logic for initializing different components that need to be initialized when running tests." (:require [clojure.string :as str] [colorize.core :as colorize] + [metabase + [config :as config] + [util :as u]] [metabase.plugins.classloader :as classloader])) + +;; (def ^:private ^:dynamic *require-chain* nil) + +;; (defonce new-require +;; (let [orig-require (var-get #'clojure.core/require)] +;; (orig-require 'clojure.pprint) +;; (fn [& args] +;; (binding [*require-chain* (conj (vec *require-chain*) (ns-name *ns*))] +;; (let [require-chain-description (apply str (interpose " -> " *require-chain*))] +;; (println "\nin" require-chain-description) +;; ((resolve 'clojure.pprint/pprint) (cons 'require args)) +;; (apply orig-require args) +;; (println "finished" require-chain-description)))))) + +;; (intern 'clojure.core 'require new-require) + (defmulti initialize-if-needed! "Initialize one or more components. @@ -37,19 +56,38 @@ (str/join "\n" [border body border]) "\n"))))) +(def ^:private init-timeout-ms (* 30 1000)) + +(def ^:private ^:dynamic *initializing* []) + +(defn- deref-init-delay [task-name a-delay] + (try + (when (contains? (set *initializing*) task-name) + (throw (Exception. (format "Circular initialization dependencies! %s" + (str/join " -> " (conj *initializing* task-name)))))) + (binding [*initializing* (conj *initializing* task-name)] + (u/with-timeout init-timeout-ms + @a-delay)) + (catch Throwable e + (println "Error initializing" task-name) + (println e) + (when config/is-test? + (System/exit -1)) + (throw e)))) + (defmacro ^:private define-initialization [task-name & body] - (let [delay-symb (vary-meta (symbol (format "init-%s-%d" (name task-name) (hash &form))) - assoc :private true)] + (let [delay-symb (-> (symbol (format "init-%s-%d" (name task-name) (hash &form))) + (with-meta {:private true}))] `(do (defonce ~delay-symb (delay (log-init-message ~(keyword task-name)) (swap! initialized conj ~(keyword task-name)) ~@body - nil)) + ~(keyword task-name))) (defmethod initialize-if-needed! ~(keyword task-name) [~'_] - @~delay-symb)))) + (deref-init-delay ~(keyword task-name) ~delay-symb))))) (define-initialization :plugins (classloader/require 'metabase.test.initialize.plugins) diff --git a/test/metabase/test/initialize/web_server.clj b/test/metabase/test/initialize/web_server.clj index f4c966811d0..c4efd7dd601 100644 --- a/test/metabase/test/initialize/web_server.clj +++ b/test/metabase/test/initialize/web_server.clj @@ -1,17 +1,39 @@ (ns metabase.test.initialize.web-server - (:require [clojure.tools.logging :as log] - [metabase + (:require [metabase [config :as config] [handler :as handler] [server :as server]] [metabase.core.initialization-status :as init-status] [metabase.models.setting :as setting])) +(defn- test-handler + ([request] + (try + (#'handler/app request) + (catch Throwable e + (println "ERROR HANDLING REQUEST! <sync>" request) + (println e) + (throw e)))) + + ([request respond raise] + (letfn [(raise' [e] + (println "ERROR HANDLING REQUEST! <async raise>" request) + (println e) + (raise e))] + (try + (#'handler/app request respond raise') + (catch Throwable e + (println "ERROR HANDLING REQUEST! <async thrown>" request) + (println e) + (throw e)))))) + (defn init! [] (try - (server/start-web-server! #'handler/app) + (server/start-web-server! test-handler) + (printf "Started test server on port %d\n" (config/config-int :mb-jetty-port)) (catch Throwable e - (log/error e "Web server failed to start") + (println "Web server failed to start") + (println e) (when config/is-test? (System/exit -2)))) (init-status/set-complete!) diff --git a/test/metabase/test/util.clj b/test/metabase/test/util.clj index 01065bd9e2f..ce35d72c8cb 100644 --- a/test/metabase/test/util.clj +++ b/test/metabase/test/util.clj @@ -1,14 +1,14 @@ (ns metabase.test.util "Helper functions and macros for writing unit tests." (:require [cheshire.core :as json] - [clj-time.core :as time] [clojure [string :as str] - [test :as t] + [test :refer :all] [walk :as walk]] [clojure.tools.logging :as log] [clojurewerkz.quartzite.scheduler :as qs] [colorize.core :as colorize] + [java-time :as t] [metabase [driver :as driver] [task :as task] @@ -37,7 +37,6 @@ [metabase.test [data :as data] [initialize :as initialize]] - [metabase.util.date :as du] [schema.core :as s] [toucan.db :as db] [toucan.util.test :as tt]) @@ -45,13 +44,13 @@ org.apache.log4j.Logger [org.quartz CronTrigger JobDetail JobKey Scheduler Trigger])) -(defmethod t/assert-expr 'schema= +(defmethod assert-expr 'schema= [message form] (let [[_ schema actual] form] `(let [schema# ~schema actual# ~actual pass?# (nil? (s/check schema# actual#))] - (t/do-report + (do-report {:type (if pass?# :pass :fail) :message ~message :expected (s/explain schema#) @@ -64,9 +63,9 @@ {:style/indent 0} [expected actual] (let [symb (symbol (format "expect-schema-%d" (hash &form)))] - `(t/deftest ~symb - (t/testing (format ~(str (ns-name *ns*) ":%s") (:line (meta (var ~symb)))) - (t/is (~'schema= ~expected ~actual)))))) + `(deftest ~symb + (testing (format ~(str (ns-name *ns*) ":%s") (:line (meta (var ~symb)))) + (is (~'schema= ~expected ~actual)))))) (defn- random-uppercase-letter [] (char (+ (int \A) (rand-int 26)))) @@ -207,13 +206,13 @@ (u/strict-extend (class TaskHistory) tt/WithTempDefaults {:with-temp-defaults (fn [_] - (let [started (time/now) - ended (time/plus started (time/millis 10))] + (let [started (t/zoned-date-time) + ended (t/plus started (t/millis 10))] {:db_id (data/id) :task (random-name) - :started_at (du/->Timestamp started) - :ended_at (du/->Timestamp ended) - :duration (du/calculate-duration started ended)}))}) + :started_at started + :ended_at ended + :duration (.toMillis (t/duration started ended))}))}) (u/strict-extend (class User) tt/WithTempDefaults @@ -269,7 +268,7 @@ (setting/get setting-k))] (try (setting/set! setting-k value) - (t/testing (colorize/blue (format "Setting %s = %s" (keyword setting-k) value)) + (testing (colorize/blue (format "Setting %s = %s" (keyword setting-k) value)) (f)) (finally (setting/set! setting-k original-value))))) diff --git a/test/metabase/test/util/timezone.clj b/test/metabase/test/util/timezone.clj index 23823ec5fb0..2c0473ac7b5 100644 --- a/test/metabase/test/util/timezone.clj +++ b/test/metabase/test/util/timezone.clj @@ -1,57 +1,8 @@ (ns metabase.test.util.timezone - (:require [clj-time.core :as time] - [clojure.test :as t] + (:require [clojure.test :as t] [metabase.driver :as driver] - [metabase.test.initialize :as initialize] - [metabase.util.date :as du]) - (:import java.util.TimeZone - org.joda.time.DateTimeZone)) - -(defn- ^:deprecated ->datetimezone ^DateTimeZone [timezone] - (cond - (instance? DateTimeZone timezone) - timezone - - (string? timezone) - (DateTimeZone/forID timezone) - - (instance? TimeZone timezone) - (DateTimeZone/forTimeZone timezone))) - -(defn ^:deprecated call-with-jvm-tz - "Invokes the thunk `F` with the JVM timezone set to `DTZ` (String or instance of TimeZone or DateTimeZone), puts the - various timezone settings back the way it found it when it exits." - [dtz thunk] - (initialize/initialize-if-needed! :db :plugins) - (let [dtz (->datetimezone dtz) - orig-tz (TimeZone/getDefault) - orig-dtz (time/default-time-zone) - orig-tz-prop (System/getProperty "user.timezone")] - (try - ;; It looks like some DB drivers cache the timezone information when instantiated, this clears those to force - ;; them to reread that timezone value - (#'driver/notify-all-databases-updated) - ;; Used by JDBC, and most JVM things - (TimeZone/setDefault (.toTimeZone dtz)) - ;; Needed as Joda time has a different default TZ - (DateTimeZone/setDefault dtz) - ;; We read the system property directly when formatting results, so this needs to be changed - (System/setProperty "user.timezone" (.getID dtz)) - (with-redefs [du/jvm-timezone (delay (.toTimeZone dtz))] - (t/testing (format "JVM timezone set to %s" (.getID dtz)) - (thunk))) - (finally - ;; We need to ensure we always put the timezones back the way - ;; we found them as it will cause test failures - (TimeZone/setDefault orig-tz) - (DateTimeZone/setDefault orig-dtz) - (System/setProperty "user.timezone" orig-tz-prop))))) - -(defmacro ^:deprecated with-jvm-tz - "Invokes `body` with the JVM timezone set to `dtz`. DEPRECATED because this uses Joda-Time. Use - `with-system-timezone-id` instead!" - [^DateTimeZone dtz & body] - `(call-with-jvm-tz ~dtz (fn [] ~@body))) + [metabase.test.initialize :as initialize]) + (:import java.util.TimeZone)) (defn do-with-system-timezone-id [^String timezone-id thunk] ;; only if the app DB is already set up, we need to make sure plugins are loaded and kill any connection pools that diff --git a/test/metabase/util/date_2_test.clj b/test/metabase/util/date_2_test.clj index 10548534b0b..b3db60a5774 100644 --- a/test/metabase/util/date_2_test.clj +++ b/test/metabase/util/date_2_test.clj @@ -136,12 +136,22 @@ ;; TODO - more tests! (deftest format-test - (is (= "2019-11-01 18:39:00-07:00" - (u.date/format-sql (t/zoned-date-time "2019-11-01T18:39:00-07:00[US/Pacific]"))))) + (testing "ZonedDateTime" + (is (= "2019-11-01T18:39:00-07:00" + (u.date/format (t/zoned-date-time "2019-11-01T18:39:00-07:00[US/Pacific]"))) + "should get formatted as the same way as an OffsetDateTime")) + (testing "Instant" + (is (= "1970-01-01T00:00:00Z" + (u.date/format (t/instant "1970-01-01T00:00:00Z")))))) (deftest format-sql-test - (is (= "2019-11-05 19:27:00" - (u.date/format-sql (t/local-date-time "2019-11-05T19:27"))))) + (testing "LocalDateTime" + (is (= "2019-11-05 19:27:00" + (u.date/format-sql (t/local-date-time "2019-11-05T19:27"))))) + (testing "ZonedDateTime" + (is (= "2019-11-01 18:39:00-07:00" + (u.date/format-sql (t/zoned-date-time "2019-11-01T18:39:00-07:00[US/Pacific]"))) + "should get formatted as the same way as an OffsetDateTime"))) (deftest extract-test (testing "u.date/extract with 2 args" @@ -341,3 +351,28 @@ (testing "exclusive start" (is (= {:start (t/local-date-time "2019-11-17T23:59")} (comparison-range :>= {:start :exclusive})))))))) + +(deftest period-duration-test + (testing "Creating a period duration from a string" + (is (= (org.threeten.extra.PeriodDuration/of (t/duration "PT59S")) + (u.date/period-duration "PT59S")))) + (testing "Creating a period duration out of two temporal types of the same class" + (is (= (u.date/period-duration "PT1S") + (u.date/period-duration (t/offset-date-time "2019-12-03T02:30:05Z") (t/offset-date-time "2019-12-03T02:30:06Z"))))) + (testing "Creating a period duration out of two different temporal types" + (is (= (u.date/period-duration "PT59S") + (u.date/period-duration (t/instant "2019-12-03T02:30:27Z") (t/offset-date-time "2019-12-03T02:31:26Z")))))) + +(deftest older-than-test + (let [now (t/instant "2019-12-04T00:45:00Z")] + (t/with-clock (t/mock-clock (t/zone-id "America/Los_Angeles")) + (testing (str "now = " now) + (doseq [t ((juxt t/instant t/local-date t/local-date-time t/offset-date-time identity) + (t/zoned-date-time "2019-11-01T00:00-08:00[US/Pacific]"))] + (testing (format "t = %s" (pr-str t)) + (is (= true + (u.date/older-than? t (t/weeks 2))) + (format "%s happened before 2019-11-19" (pr-str t))) + (is (= false + (u.date/older-than? t (t/months 2))) + (format "%s did not happen before 2019-10-03" (pr-str t))))))))) diff --git a/test/metabase/util/date_test.clj b/test/metabase/util/date_test.clj deleted file mode 100644 index 181ea85b319..00000000000 --- a/test/metabase/util/date_test.clj +++ /dev/null @@ -1,111 +0,0 @@ -(ns metabase.util.date-test - (:require [clojure.test :refer :all] - [metabase.util.date :as du])) - -(def ^:private saturday-the-31st #inst "2005-12-31T19:05:55") -(def ^:private sunday-the-1st #inst "2006-01-01T04:18:26") -(def ^:private with-milliseconds #inst "2019-09-24T15:07:30.555") - -(deftest is-temporal-test - (are [expected arg] (= expected - (du/is-temporal? arg)) - false nil - false 123 - false "abc" - false [1 2 3] - false {:a "b"} - true saturday-the-31st)) - -(deftest ->Timestamp-test - (are [actual] (= saturday-the-31st - actual) - (du/->Timestamp (du/->Date saturday-the-31st)) - (du/->Timestamp (du/->Calendar saturday-the-31st)) - (du/->Timestamp (du/->Calendar (.getTime saturday-the-31st))) - (du/->Timestamp (.getTime saturday-the-31st)) - (du/->Timestamp "2005-12-31T19:05:55+00:00" du/utc))) - -(deftest ->iso-8601-datetime-test - (are [expected inst timezone] (= expected - (du/->iso-8601-datetime inst timezone)) - nil nil nil - "2005-12-31T19:05:55.000Z" saturday-the-31st nil - "2005-12-31T11:05:55.000-08:00" saturday-the-31st "US/Pacific" - "2006-01-01T04:05:55.000+09:00" saturday-the-31st "Asia/Tokyo")) - - -(deftest date-extract-test - (testing "UTC timezone" - (are [expected unit inst] (= expected - (du/date-extract unit inst "UTC")) - 5 :minute-of-hour saturday-the-31st - 19 :hour-of-day saturday-the-31st - 7 :day-of-week saturday-the-31st - 1 :day-of-week sunday-the-1st - 31 :day-of-month saturday-the-31st - 365 :day-of-year saturday-the-31st - 53 :week-of-year saturday-the-31st - 12 :month-of-year saturday-the-31st - 4 :quarter-of-year saturday-the-31st - 2005 :year saturday-the-31st)) - (testing "US/Pacific timezone" - (are [expected unit inst] (= expected - (du/date-extract unit inst "US/Pacific")) - 5 :minute-of-hour saturday-the-31st - 11 :hour-of-day saturday-the-31st - 7 :day-of-week saturday-the-31st - 7 :day-of-week sunday-the-1st - 31 :day-of-month saturday-the-31st - 365 :day-of-year saturday-the-31st - 53 :week-of-year saturday-the-31st - 12 :month-of-year saturday-the-31st - 4 :quarter-of-year saturday-the-31st - 2005 :year saturday-the-31st)) - (testing "Asia/Tokyo timezone" - (are [expected unit inst] (= expected - (du/date-extract unit inst "Asia/Tokyo")) - 5 :minute-of-hour saturday-the-31st - 4 :hour-of-day saturday-the-31st - 1 :day-of-week saturday-the-31st - 1 :day-of-week sunday-the-1st - 1 :day-of-month saturday-the-31st - 1 :day-of-year saturday-the-31st - 1 :week-of-year saturday-the-31st - 1 :month-of-year saturday-the-31st - 1 :quarter-of-year saturday-the-31st - 2006 :year saturday-the-31st))) - -(deftest date-trunc-test - (testing "UTC timezone" - (are [expected unit inst] (= expected - (du/date-trunc unit inst "UTC")) - #inst "2019-09-24T15:07:30" :second with-milliseconds - #inst "2005-12-31T19:05" :minute saturday-the-31st - #inst "2005-12-31T19:00" :hour saturday-the-31st - #inst "2005-12-31" :day saturday-the-31st - #inst "2005-12-25" :week saturday-the-31st - #inst "2006-01-01" :week sunday-the-1st - #inst "2005-12-01" :month saturday-the-31st - #inst "2005-10-01" :quarter saturday-the-31st)) - (testing "US/Pacific timezone" - (are [expected unit inst] (= expected - (du/date-trunc unit inst "US/Pacific")) - #inst "2019-09-24T15:07:30" :second with-milliseconds - #inst "2005-12-31T19:05" :minute saturday-the-31st - #inst "2005-12-31T19:00" :hour saturday-the-31st - #inst "2005-12-31-08:00" :day saturday-the-31st - #inst "2005-12-25-08:00" :week saturday-the-31st - #inst "2005-12-25-08:00" :week sunday-the-1st - #inst "2005-12-01-08:00" :month saturday-the-31st - #inst "2005-10-01-08:00" :quarter saturday-the-31st)) - (testing "Asia/Tokyo timezone" - (are [expected unit inst] (= expected - (du/date-trunc unit inst "Asia/Tokyo")) - #inst "2019-09-24T15:07:30" :second with-milliseconds - #inst "2005-12-31T19:05" :minute saturday-the-31st - #inst "2005-12-31T19:00" :hour saturday-the-31st - #inst "2006-01-01+09:00" :day saturday-the-31st - #inst "2006-01-01+09:00" :week saturday-the-31st - #inst "2006-01-01+09:00" :week sunday-the-1st - #inst "2006-01-01+09:00" :month saturday-the-31st - #inst "2006-01-01+09:00" :quarter saturday-the-31st))) -- GitLab