Add geocode endpoint
parent
7e0e8ac168
commit
c0c89ec8f7
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,91 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Geocoder\Query\GeocodeQuery;
|
||||||
|
|
||||||
|
$address = urldecode($VARS["address"]);
|
||||||
|
$origaddress = $address;
|
||||||
|
|
||||||
|
$cacheresp = $memcache->get("gis.geocode." . sha1($origaddress));
|
||||||
|
if ($cacheresp !== false && empty($VARS["nocache"])) {
|
||||||
|
exitWithJson(json_decode($cacheresp, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
$geocoder = new \Geocoder\ProviderAggregator();
|
||||||
|
$adapter = new \Http\Adapter\Guzzle7\Client();
|
||||||
|
|
||||||
|
$chain = new \Geocoder\Provider\Chain\Chain([
|
||||||
|
new \Geocoder\Provider\Mapbox\Mapbox($adapter, env("mapbox_key")),
|
||||||
|
new \Geocoder\Provider\MapQuest\MapQuest($adapter, env("mapquest_key"))
|
||||||
|
]);
|
||||||
|
|
||||||
|
$geocoder->registerProvider($chain);
|
||||||
|
|
||||||
|
$query = GeocodeQuery::create($address)->withLimit(1);
|
||||||
|
|
||||||
|
if (isset($VARS["country"]) && preg_match("/^[A-Z]{2}$/", $VARS["country"])) {
|
||||||
|
$query = $query->withData("country", $VARS["country"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$results = $geocoder->geocodeQuery($query);
|
||||||
|
|
||||||
|
if ($results->count() > 0) {
|
||||||
|
$result = $results->first();
|
||||||
|
} else {
|
||||||
|
$output = [
|
||||||
|
"status" => "ERROR",
|
||||||
|
"message" => "Address not found.",
|
||||||
|
"address" => [
|
||||||
|
"original" => $origaddress,
|
||||||
|
"street" => "",
|
||||||
|
"postalCode" => ""
|
||||||
|
],
|
||||||
|
"coords" => [
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"accuracy" => [
|
||||||
|
"ok" => false
|
||||||
|
],
|
||||||
|
"provider" => ""
|
||||||
|
];
|
||||||
|
goto cacheout;
|
||||||
|
}
|
||||||
|
|
||||||
|
$geolatitude = $result->getCoordinates()->getLatitude();
|
||||||
|
$geolongitude = $result->getCoordinates()->getLongitude();
|
||||||
|
|
||||||
|
$accurate = !empty($result->getStreetNumber());
|
||||||
|
|
||||||
|
if ($accurate) {
|
||||||
|
$address = implode(" ", [$result->getStreetNumber(), $result->getStreetName()]);
|
||||||
|
} else {
|
||||||
|
$address = $result->getStreetName();
|
||||||
|
}
|
||||||
|
|
||||||
|
$output = [
|
||||||
|
"status" => "OK",
|
||||||
|
"address" => [
|
||||||
|
"original" => $origaddress,
|
||||||
|
"street" => ucwords(strtolower(StreetNormalizer::normalizeAddress($address))),
|
||||||
|
"postalCode" => $result->getPostalCode()
|
||||||
|
],
|
||||||
|
"coords" => [
|
||||||
|
$geolatitude,
|
||||||
|
$geolongitude
|
||||||
|
],
|
||||||
|
"accuracy" => [
|
||||||
|
"ok" => $accurate
|
||||||
|
],
|
||||||
|
"provider" => $result->getProvidedBy()
|
||||||
|
];
|
||||||
|
|
||||||
|
cacheout:
|
||||||
|
$memcache->set("gis.geocode." . sha1($origaddress), json_encode($output));
|
||||||
|
exitWithJson($output);
|
@ -0,0 +1,471 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class StreetNormalizer {
|
||||||
|
/*
|
||||||
|
* Machine-readable form of the chart at
|
||||||
|
* https://pe.usps.com/text/pub28/28apc_002.htm
|
||||||
|
* with loop->lp added
|
||||||
|
*/
|
||||||
|
|
||||||
|
const SUFFIX_TABLE = [
|
||||||
|
'ALLEE' => 'ALY',
|
||||||
|
'ALLEY' => 'ALY',
|
||||||
|
'ALLY' => 'ALY',
|
||||||
|
'ANEX' => 'ANX',
|
||||||
|
'ANNEX' => 'ANX',
|
||||||
|
'ANNX' => 'ANX',
|
||||||
|
'ARC ' => 'ARC',
|
||||||
|
'ARCADE ' => 'ARC',
|
||||||
|
'AV' => 'AVE',
|
||||||
|
'AVEN' => 'AVE',
|
||||||
|
'AVENU' => 'AVE',
|
||||||
|
'AVENUE' => 'AVE',
|
||||||
|
'AVN' => 'AVE',
|
||||||
|
'AVNUE' => 'AVE',
|
||||||
|
'BAYOO' => 'BYU',
|
||||||
|
'BAYOU' => 'BYU',
|
||||||
|
'BEACH' => 'BCH',
|
||||||
|
'BEND' => 'BND',
|
||||||
|
'BLUF' => 'BLF',
|
||||||
|
'BLUFF' => 'BLF',
|
||||||
|
'BLUFFS ' => 'BLFS',
|
||||||
|
'BOT' => 'BTM',
|
||||||
|
'BOTTM' => 'BTM',
|
||||||
|
'BOTTOM' => 'BTM',
|
||||||
|
'BOUL' => 'BLVD',
|
||||||
|
'BOULEVARD ' => 'BLVD',
|
||||||
|
'BOULV' => 'BLVD',
|
||||||
|
'BRNCH' => 'BR',
|
||||||
|
'BRANCH' => 'BR',
|
||||||
|
'BRDGE' => 'BRG',
|
||||||
|
'BRIDGE' => 'BRG',
|
||||||
|
'BROOK' => 'BRK',
|
||||||
|
'BROOKS ' => 'BRKS',
|
||||||
|
'BURG' => 'BG',
|
||||||
|
'BURGS' => 'BGS',
|
||||||
|
'BYPA' => 'BYP',
|
||||||
|
'BYPAS' => 'BYP',
|
||||||
|
'BYPASS' => 'BYP',
|
||||||
|
'BYPS' => 'BYP',
|
||||||
|
'CAMP' => 'CP',
|
||||||
|
'CMP' => 'CP',
|
||||||
|
'CANYN' => 'CYN',
|
||||||
|
'CANYON' => 'CYN',
|
||||||
|
'CNYN' => 'CYN',
|
||||||
|
'CAPE' => 'CPE',
|
||||||
|
'CAUSEWAY' => 'CSWY',
|
||||||
|
'CAUSWA' => 'CSWY',
|
||||||
|
'CEN' => 'CTR',
|
||||||
|
'CENT' => 'CTR',
|
||||||
|
'CENTER' => 'CTR',
|
||||||
|
'CENTR' => 'CTR',
|
||||||
|
'CENTRE' => 'CTR',
|
||||||
|
'CNTER' => 'CTR',
|
||||||
|
'CNTR' => 'CTR',
|
||||||
|
'CENTERS ' => 'CTRS',
|
||||||
|
'CIRC' => 'CIR',
|
||||||
|
'CIRCL' => 'CIR',
|
||||||
|
'CIRCLE' => 'CIR',
|
||||||
|
'CRCL' => 'CIR',
|
||||||
|
'CRCLE' => 'CIR',
|
||||||
|
'CIRCLES' => 'CIRS',
|
||||||
|
'CLIFF' => 'CLF',
|
||||||
|
'CLIFFS' => 'CLFS',
|
||||||
|
'CLUB' => 'CLB',
|
||||||
|
'COMMON' => 'CMN',
|
||||||
|
'COMMONS' => 'CMNS',
|
||||||
|
'CORNER' => 'COR',
|
||||||
|
'CORNERS' => 'CORS',
|
||||||
|
'COURSE' => 'CRSE',
|
||||||
|
'COURT' => 'CT',
|
||||||
|
'COURTS' => 'CTS',
|
||||||
|
'COVE' => 'CV',
|
||||||
|
'COVES' => 'CVS',
|
||||||
|
'CREEK' => 'CRK',
|
||||||
|
'CRESCENT' => 'CRES',
|
||||||
|
'CRSENT' => 'CRES',
|
||||||
|
'CRSNT' => 'CRES',
|
||||||
|
'CREST' => 'CRST',
|
||||||
|
'CROSSING ' => 'XING',
|
||||||
|
'CRSSNG ' => 'XING',
|
||||||
|
'XING ' => 'XING',
|
||||||
|
'CROSSROAD' => 'XRD',
|
||||||
|
'CROSSROADS' => 'XRDS',
|
||||||
|
'CURVE ' => 'CURV',
|
||||||
|
'DALE ' => 'DL',
|
||||||
|
'DL ' => 'DL',
|
||||||
|
'DAM ' => 'DM',
|
||||||
|
'DM ' => 'DM',
|
||||||
|
'DIV' => 'DV',
|
||||||
|
'DIVIDE' => 'DV',
|
||||||
|
'DVD' => 'DV',
|
||||||
|
'DRIV' => 'DR',
|
||||||
|
'DRIVE' => 'DR',
|
||||||
|
'DRV' => 'DR',
|
||||||
|
'DRIVES' => 'DRS',
|
||||||
|
'ESTATE' => 'EST',
|
||||||
|
'ESTATES' => 'ESTS',
|
||||||
|
'EXP' => 'EXPY',
|
||||||
|
'EXPR' => 'EXPY',
|
||||||
|
'EXPRESS' => 'EXPY',
|
||||||
|
'EXPRESSWAY' => 'EXPY',
|
||||||
|
'EXPW' => 'EXPY',
|
||||||
|
'EXTENSION' => 'EXT',
|
||||||
|
'EXTN' => 'EXT',
|
||||||
|
'EXTNSN' => 'EXT',
|
||||||
|
'FALLS' => 'FLS',
|
||||||
|
'FERRY' => 'FRY',
|
||||||
|
'FRRY' => 'FRY',
|
||||||
|
'FIELD' => 'FLD',
|
||||||
|
'FIELDS' => 'FLDS',
|
||||||
|
'FLAT' => 'FLT',
|
||||||
|
'FLATS' => 'FLTS',
|
||||||
|
'FORD' => 'FRD',
|
||||||
|
'FORDS' => 'FRDS',
|
||||||
|
'FOREST' => 'FRST',
|
||||||
|
'FORESTS' => 'FRST',
|
||||||
|
'FORG' => 'FRG',
|
||||||
|
'FORGE' => 'FRG',
|
||||||
|
'FORGES' => 'FRGS',
|
||||||
|
'FORK' => 'FRK',
|
||||||
|
'FORKS' => 'FRKS',
|
||||||
|
'FORT' => 'FT',
|
||||||
|
'FRT' => 'FT',
|
||||||
|
'FREEWAY' => 'FWY',
|
||||||
|
'FREEWY' => 'FWY',
|
||||||
|
'FRWAY' => 'FWY',
|
||||||
|
'FRWY' => 'FWY',
|
||||||
|
'GARDEN' => 'GDN',
|
||||||
|
'GARDN' => 'GDN',
|
||||||
|
'GRDEN' => 'GDN',
|
||||||
|
'GRDN' => 'GDN',
|
||||||
|
'GARDENS' => 'GDNS',
|
||||||
|
'GRDNS' => 'GDNS',
|
||||||
|
'GATEWAY' => 'GTWY',
|
||||||
|
'GATEWY' => 'GTWY',
|
||||||
|
'GATWAY' => 'GTWY',
|
||||||
|
'GTWAY' => 'GTWY',
|
||||||
|
'GLEN' => 'GLN',
|
||||||
|
'GLENS' => 'GLNS',
|
||||||
|
'GREEN' => 'GRN',
|
||||||
|
'GREENS' => 'GRNS',
|
||||||
|
'GROV' => 'GRV',
|
||||||
|
'GROVE' => 'GRV',
|
||||||
|
'GROVES' => 'GRVS',
|
||||||
|
'HARB' => 'HBR',
|
||||||
|
'HARBOR' => 'HBR',
|
||||||
|
'HARBR' => 'HBR',
|
||||||
|
'HRBOR' => 'HBR',
|
||||||
|
'HARBORS' => 'HBRS',
|
||||||
|
'HAVEN' => 'HVN',
|
||||||
|
'HT' => 'HTS',
|
||||||
|
'HIGHWAY' => 'HWY',
|
||||||
|
'HIGHWY' => 'HWY',
|
||||||
|
'HIWAY' => 'HWY',
|
||||||
|
'HIWY' => 'HWY',
|
||||||
|
'HWAY' => 'HWY',
|
||||||
|
'HILL' => 'HL',
|
||||||
|
'HILLS' => 'HLS',
|
||||||
|
'HLLW' => 'HOLW',
|
||||||
|
'HOLLOW' => 'HOLW',
|
||||||
|
'HOLLOWS' => 'HOLW',
|
||||||
|
'HOLWS' => 'HOLW',
|
||||||
|
'ISLAND' => 'IS',
|
||||||
|
'ISLND' => 'IS',
|
||||||
|
'ISLANDS' => 'ISS',
|
||||||
|
'ISLNDS' => 'ISS',
|
||||||
|
'ISLES' => 'ISLE',
|
||||||
|
'JCTION' => 'JCT',
|
||||||
|
'JCTN' => 'JCT',
|
||||||
|
'JUNCTION' => 'JCT',
|
||||||
|
'JUNCTN' => 'JCT',
|
||||||
|
'JUNCTON' => 'JCT',
|
||||||
|
'JCTNS' => 'JCTS',
|
||||||
|
'JUNCTIONS' => 'JCTS',
|
||||||
|
'KEY' => 'KY',
|
||||||
|
'KEYS' => 'KYS',
|
||||||
|
'KNL' => 'KNL',
|
||||||
|
'KNOL' => 'KNL',
|
||||||
|
'KNOLL' => 'KNL',
|
||||||
|
'KNOLLS' => 'KNLS',
|
||||||
|
'LAKE' => 'LK',
|
||||||
|
'LAKES' => 'LKS',
|
||||||
|
'LANDING' => 'LNDG',
|
||||||
|
'LNDNG' => 'LNDG',
|
||||||
|
'LANE' => 'LN',
|
||||||
|
'LIGHT' => 'LGT',
|
||||||
|
'LIGHTS' => 'LGTS',
|
||||||
|
'LOAF' => 'LF',
|
||||||
|
'LOCK' => 'LCK',
|
||||||
|
'LOCKS' => 'LCKS',
|
||||||
|
'LDGE' => 'LDG',
|
||||||
|
'LODG' => 'LDG',
|
||||||
|
'LODGE' => 'LDG',
|
||||||
|
'MANOR' => 'MNR',
|
||||||
|
'MANORS' => 'MNRS',
|
||||||
|
'MEADOW' => 'MDW',
|
||||||
|
'MDW' => 'MDWS',
|
||||||
|
'MEADOWS' => 'MDWS',
|
||||||
|
'MEDOWS' => 'MDWS',
|
||||||
|
'MILL' => 'ML',
|
||||||
|
'MILLS' => 'MLS',
|
||||||
|
'MISSN' => 'MSN',
|
||||||
|
'MSSN' => 'MSN',
|
||||||
|
'MOTORWAY' => 'MTWY',
|
||||||
|
'MNT' => 'MT',
|
||||||
|
'MOUNT' => 'MT',
|
||||||
|
'MNTAIN' => 'MTN',
|
||||||
|
'MNTN' => 'MTN',
|
||||||
|
'MOUNTAIN' => 'MTN',
|
||||||
|
'MOUNTIN' => 'MTN',
|
||||||
|
'MTIN' => 'MTN',
|
||||||
|
'MNTNS' => 'MTNS',
|
||||||
|
'MOUNTAINS' => 'MTNS',
|
||||||
|
'NECK' => 'NCK',
|
||||||
|
'ORCHARD' => 'ORCH',
|
||||||
|
'ORCHRD' => 'ORCH',
|
||||||
|
'OVL' => 'OVAL',
|
||||||
|
'OVERPASS' => 'OPAS',
|
||||||
|
'PRK' => 'PARK',
|
||||||
|
'PARKS' => 'PARK',
|
||||||
|
'PARKWAY' => 'PKWY',
|
||||||
|
'PARKWY' => 'PKWY',
|
||||||
|
'PKWAY' => 'PKWY',
|
||||||
|
'PKY' => 'PKWY',
|
||||||
|
'PARKWAYS' => 'PKWY',
|
||||||
|
'PKWYS' => 'PKWY',
|
||||||
|
'PASSAGE' => 'PSGE',
|
||||||
|
'PATHS' => 'PATH',
|
||||||
|
'PIKES' => 'PIKE',
|
||||||
|
'PINE' => 'PNE',
|
||||||
|
'PINES' => 'PNES',
|
||||||
|
'PLAIN' => 'PLN',
|
||||||
|
'PLAINS' => 'PLNS',
|
||||||
|
'PLAZA' => 'PLZ',
|
||||||
|
'PLZA' => 'PLZ',
|
||||||
|
'POINT' => 'PT',
|
||||||
|
'POINTS' => 'PTS',
|
||||||
|
'PORT' => 'PRT',
|
||||||
|
'PORTS' => 'PRTS',
|
||||||
|
'PRAIRIE' => 'PR',
|
||||||
|
'PRR' => 'PR',
|
||||||
|
'RAD' => 'RADL',
|
||||||
|
'RADIAL' => 'RADL',
|
||||||
|
'RADIEL' => 'RADL',
|
||||||
|
'RANCH' => 'RNCH',
|
||||||
|
'RANCHES' => 'RNCH',
|
||||||
|
'RNCHS' => 'RNCH',
|
||||||
|
'RAPID' => 'RPD',
|
||||||
|
'RAPIDS' => 'RPDS',
|
||||||
|
'REST' => 'RST',
|
||||||
|
'RDGE' => 'RDG',
|
||||||
|
'RIDGE' => 'RDG',
|
||||||
|
'RIDGES' => 'RDGS',
|
||||||
|
'RIVER' => 'RIV',
|
||||||
|
'RVR' => 'RIV',
|
||||||
|
'RIVR' => 'RIV',
|
||||||
|
'ROAD' => 'RD',
|
||||||
|
'ROADS' => 'RDS',
|
||||||
|
'ROUTE' => 'RTE',
|
||||||
|
'SHOAL' => 'SHL',
|
||||||
|
'SHOALS' => 'SHLS',
|
||||||
|
'SHOAR' => 'SHR',
|
||||||
|
'SHORE' => 'SHR',
|
||||||
|
'SHOARS' => 'SHRS',
|
||||||
|
'SHORES' => 'SHRS',
|
||||||
|
'SKYWAY' => 'SKWY',
|
||||||
|
'SPNG' => 'SPG',
|
||||||
|
'SPRING' => 'SPG',
|
||||||
|
'SPRNG' => 'SPG',
|
||||||
|
'SPNGS' => 'SPGS',
|
||||||
|
'SPRINGS' => 'SPGS',
|
||||||
|
'SPRNGS' => 'SPGS',
|
||||||
|
'SPURS' => 'SPUR',
|
||||||
|
'SQR' => 'SQ',
|
||||||
|
'SQRE' => 'SQ',
|
||||||
|
'SQU' => 'SQ',
|
||||||
|
'SQUARE' => 'SQ',
|
||||||
|
'SQRS' => 'SQS',
|
||||||
|
'SQUARES' => 'SQS',
|
||||||
|
'STATION' => 'STA',
|
||||||
|
'STATN' => 'STA',
|
||||||
|
'STN' => 'STA',
|
||||||
|
'STRAV' => 'STRA',
|
||||||
|
'STRAVEN' => 'STRA',
|
||||||
|
'STRAVENUE' => 'STRA',
|
||||||
|
'STRAVN' => 'STRA',
|
||||||
|
'STRVN' => 'STRA',
|
||||||
|
'STRVNUE' => 'STRA',
|
||||||
|
'STREAM' => 'STRM',
|
||||||
|
'STREME' => 'STRM',
|
||||||
|
'STREET' => 'ST',
|
||||||
|
'STRT' => 'ST',
|
||||||
|
'STR' => 'ST',
|
||||||
|
'STREETS' => 'STS',
|
||||||
|
'SUMIT' => 'SMT',
|
||||||
|
'SUMITT' => 'SMT',
|
||||||
|
'SUMMIT' => 'SMT',
|
||||||
|
'TERR' => 'TER',
|
||||||
|
'TERRACE' => 'TER',
|
||||||
|
'THROUGHWAY' => 'TRWY',
|
||||||
|
'TRACE' => 'TRCE',
|
||||||
|
'TRACES' => 'TRCE',
|
||||||
|
'TRACK' => 'TRAK',
|
||||||
|
'TRACKS' => 'TRAK',
|
||||||
|
'TRK' => 'TRAK',
|
||||||
|
'TRKS' => 'TRAK',
|
||||||
|
'TRAFFICWAY' => 'TRFY',
|
||||||
|
'TRAIL' => 'TRL',
|
||||||
|
'TRAILS' => 'TRL',
|
||||||
|
'TRLS' => 'TRL',
|
||||||
|
'TRAILER' => 'TRLR',
|
||||||
|
'TRLRS' => 'TRLR',
|
||||||
|
'TUNEL' => 'TUNL',
|
||||||
|
'TUNLS' => 'TUNL',
|
||||||
|
'TUNNEL' => 'TUNL',
|
||||||
|
'TUNNELS' => 'TUNL',
|
||||||
|
'TUNNL' => 'TUNL',
|
||||||
|
'TRNPK' => 'TPKE',
|
||||||
|
'TURNPIKE' => 'TPKE',
|
||||||
|
'TURNPK' => 'TPKE',
|
||||||
|
'UNDERPASS' => 'UPAS',
|
||||||
|
'UNION' => 'UN',
|
||||||
|
'UNIONS' => 'UNS',
|
||||||
|
'VALLEY' => 'VLY',
|
||||||
|
'VALLY' => 'VLY',
|
||||||
|
'VLLY' => 'VLY',
|
||||||
|
'VALLEYS' => 'VLYS',
|
||||||
|
'VDCT' => 'VIA',
|
||||||
|
'VIADCT' => 'VIA',
|
||||||
|
'VIADUCT' => 'VIA',
|
||||||
|
'VIEW' => 'VW',
|
||||||
|
'VIEWS' => 'VWS',
|
||||||
|
'VILL' => 'VLG',
|
||||||
|
'VILLAG' => 'VLG',
|
||||||
|
'VILLAGE' => 'VLG',
|
||||||
|
'VILLG' => 'VLG',
|
||||||
|
'VILLIAGE' => 'VLG',
|
||||||
|
'VILLAGES' => 'VLGS',
|
||||||
|
'VILLE' => 'VL',
|
||||||
|
'VIST' => 'VIS',
|
||||||
|
'VISTA' => 'VIS',
|
||||||
|
'VST' => 'VIS',
|
||||||
|
'VSTA' => 'VIS',
|
||||||
|
'WALKS' => 'WALK',
|
||||||
|
'WY' => 'WAY',
|
||||||
|
'WELL' => 'WL',
|
||||||
|
'WELLS' => 'WLS'
|
||||||
|
];
|
||||||
|
const CARDINAL_TABLE = [
|
||||||
|
"NORTH" => "N",
|
||||||
|
"SOUTH" => "S",
|
||||||
|
"EAST" => "E",
|
||||||
|
"WEST" => "W",
|
||||||
|
"NORTHWEST" => "NW",
|
||||||
|
"SOUTHWEST" => "SW",
|
||||||
|
"NORTHEAST" => "NE",
|
||||||
|
"SOUTHEAST" => "SE"
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a street name (ex. Street Road)
|
||||||
|
* @param string $street
|
||||||
|
* @param bool $python Set to false to use built-in less-accurate code,
|
||||||
|
* or true to use normalize.py and https://github.com/mcmire/address_standardization
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function normalizeStreet(string $street, bool $python = true): string {
|
||||||
|
// Give the script a dummy house number so it doesn't get lost
|
||||||
|
$filler_address = "10000001 ";
|
||||||
|
|
||||||
|
return str_replace($filler_address, "", static::normalizeAddress($filler_address . $street));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize an address line (ex. 1234 street road)
|
||||||
|
* @param string $address
|
||||||
|
* @param bool $python Set to false to use built-in less-accurate code,
|
||||||
|
* or true to use normalize.py and https://github.com/mcmire/address_standardization
|
||||||
|
* @return string
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static function normalizeAddress(string $address, bool $python = true): string {
|
||||||
|
global $SETTINGS;
|
||||||
|
try {
|
||||||
|
if (empty($SETTINGS["normalize_python_script"]) || !file_exists($SETTINGS["normalize_python_script"])) {
|
||||||
|
throw new Exception("failing back to builtin: python script missing");
|
||||||
|
}
|
||||||
|
if (!$python) {
|
||||||
|
throw new Exception("failing back to builtin due to user request");
|
||||||
|
}
|
||||||
|
|
||||||
|
$escaped = escapeshellarg($address);
|
||||||
|
|
||||||
|
$json = shell_exec($SETTINGS["normalize_python_script"] . " " . $escaped);
|
||||||
|
|
||||||
|
$address = json_decode($json, true);
|
||||||
|
if (empty($address)) {
|
||||||
|
throw new Exception("failing back to builtin due to JSON error");
|
||||||
|
}
|
||||||
|
|
||||||
|
$address = $address["address_line_1"];
|
||||||
|
return $address;
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
$address = strtoupper(trim($address));
|
||||||
|
|
||||||
|
$address = static::normalizeSuffix($address);
|
||||||
|
$address = static::normalizeCardinals($address);
|
||||||
|
|
||||||
|
return $address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function findReplace(string $str, array $lookuptable) {
|
||||||
|
foreach ($lookuptable as $find => $replace) {
|
||||||
|
if ($str == $find) {
|
||||||
|
$str = $replace;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $str;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function findReplaceAll(string $str, array $lookuptable, string $seperator = " ") {
|
||||||
|
$words = explode($seperator, $str);
|
||||||
|
for ($i = 0; $i < count($words); $i++) {
|
||||||
|
$words[$i] = static::findReplace($words[$i], $lookuptable);
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode($seperator, $words);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace cardinal/compass directions with abbreviations
|
||||||
|
* @param string $street
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function normalizeCardinals(string $street): string {
|
||||||
|
$street = strtoupper(trim($street));
|
||||||
|
|
||||||
|
return static::findReplaceAll($street, static::CARDINAL_TABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the street suffix with the standard abbreviation.
|
||||||
|
* @param string $street
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function normalizeSuffix(string $street): string {
|
||||||
|
$street = strtoupper(trim($street));
|
||||||
|
|
||||||
|
$parts = explode(" ", $street);
|
||||||
|
|
||||||
|
$suffix = static::findReplace(end($parts), static::SUFFIX_TABLE);
|
||||||
|
|
||||||
|
$parts[count($parts) - 1] = $suffix;
|
||||||
|
|
||||||
|
return implode(" ", $parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,157 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* To change this license header, choose License Headers in Project Properties.
|
||||||
|
* To change this template file, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function parse_citystatezip($citystatezip) {
|
||||||
|
$citystatezip = strtoupper(trim($citystatezip));
|
||||||
|
|
||||||
|
$fullregex = "/^(.+), ?(.+)[,| |, ]([0-9]{5})(-[0-9]{4})?$/";
|
||||||
|
$citystateregex = "/^(\w+)[,\s]+([A-Z]{2,})+$/";
|
||||||
|
|
||||||
|
if (!preg_match($fullregex, $citystatezip, $matches)) {
|
||||||
|
if (!preg_match($citystateregex, $citystatezip, $matches)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
"city" => $matches[1],
|
||||||
|
"state" => $matches[2],
|
||||||
|
"zip" => isset($matches[3]) ? $matches[3] : null
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse_citystate($citystate) {
|
||||||
|
$citystate = strtoupper(trim($citystate));
|
||||||
|
|
||||||
|
$regex = "/^(\w+)[,\s]+([A-Z]{2,})+$/";
|
||||||
|
|
||||||
|
if (!preg_match($regex, $citystate, $matches)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
"city" => $matches[1],
|
||||||
|
"state" => $matches[2]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function geocode_returnresult($query) {
|
||||||
|
global $SETTINGS, $database;
|
||||||
|
|
||||||
|
$geocoder = new \Geocoder\ProviderAggregator();
|
||||||
|
$adapter = new \Http\Adapter\Guzzle6\Client();
|
||||||
|
|
||||||
|
$chain = new \Geocoder\Provider\Chain\Chain([
|
||||||
|
new \Geocoder\Provider\LocalDatabase\LocalDatabase($database),
|
||||||
|
new \Geocoder\Provider\Mapbox\Mapbox($adapter, $SETTINGS["mapbox_key"]),
|
||||||
|
new \Geocoder\Provider\MapQuest\MapQuest($adapter, $SETTINGS["mapquest_key"])
|
||||||
|
]);
|
||||||
|
|
||||||
|
$geocoder->registerProvider($chain);
|
||||||
|
|
||||||
|
$results = $geocoder->geocodeQuery($query);
|
||||||
|
|
||||||
|
|
||||||
|
if ($results->count() > 0) {
|
||||||
|
$result = $results->first();
|
||||||
|
} else {
|
||||||
|
header("Content-Type: application/json");
|
||||||
|
return [
|
||||||
|
"status" => "ERROR",
|
||||||
|
"message" => "That address doesn't seem to exist. If this is an error, please send the full address (including city, state, and ZIP) to support@netsyms.com.",
|
||||||
|
"address" => [
|
||||||
|
"street" => "",
|
||||||
|
"postalCode" => ""
|
||||||
|
],
|
||||||
|
"coords" => [
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"accuracy" => [
|
||||||
|
"ok" => false
|
||||||
|
],
|
||||||
|
"provider" => ""
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$geolatitude = $result->getCoordinates()->getLatitude();
|
||||||
|
$geolongitude = $result->getCoordinates()->getLongitude();
|
||||||
|
|
||||||
|
$accurate = !empty($result->getStreetNumber());
|
||||||
|
|
||||||
|
if ($accurate) {
|
||||||
|
$address = implode(" ", [$result->getStreetNumber(), $result->getStreetName()]);
|
||||||
|
} else {
|
||||||
|
$address = $result->getStreetName();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return [
|
||||||
|
"status" => "OK",
|
||||||
|
"address" => [
|
||||||
|
"street" => ucwords(strtolower(StreetNormalizer::normalizeAddress($address))),
|
||||||
|
"postalCode" => $result->getPostalCode()
|
||||||
|
],
|
||||||
|
"coords" => [
|
||||||
|
$geolatitude,
|
||||||
|
$geolongitude
|
||||||
|
],
|
||||||
|
"accuracy" => [
|
||||||
|
"ok" => $accurate
|
||||||
|
],
|
||||||
|
"provider" => $result->getProvidedBy()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function geocode_addrstring($address) {
|
||||||
|
$query = Geocoder\Query\GeocodeQuery::create($address)->withLimit(1);
|
||||||
|
return geocode_returnresult($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Geocode an address.
|
||||||
|
* @global type $SETTINGS
|
||||||
|
* @param type $number
|
||||||
|
* @param type $street
|
||||||
|
* @param type $unit
|
||||||
|
* @param type $city
|
||||||
|
* @param type $state
|
||||||
|
* @param type $zip
|
||||||
|
* @param type $country
|
||||||
|
* @param type $type
|
||||||
|
* @return type
|
||||||
|
*/
|
||||||
|
function geocode($number, $street, $unit = null, $city, $state, $zip = null, $country = null, $type = null) {
|
||||||
|
$address = "$number $street $city $state";
|
||||||
|
$origaddress = "$number $street";
|
||||||
|
|
||||||
|
$query = Geocoder\Query\GeocodeQuery::create($address)
|
||||||
|
->withLimit(1)
|
||||||
|
->withData("number", $number)
|
||||||
|
->withData("street", $street)
|
||||||
|
->withData("city", $city)
|
||||||
|
->withData("state", $state);
|
||||||
|
if (!is_null($zip) && preg_match("/^([0-9]{5})/", $zip, $matches)) {
|
||||||
|
if (count($matches) > 0) {
|
||||||
|
$query = $query->withData("zip", $matches[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!is_null($unit) && !empty($unit)) {
|
||||||
|
$query = $query->withData("unit", $unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_null($type) && count(array_diff(explode("|", $type), $SETTINGS["address_types"])) == 0) {
|
||||||
|
$query = $query->withData("locationtype", $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_null($country) && preg_match("/^[A-Z]{2}$/", $country)) {
|
||||||
|
$query = $query->withData("country", $country);
|
||||||
|
}
|
||||||
|
|
||||||
|
return geocode_returnresult($query);
|
||||||
|
}
|
Loading…
Reference in New Issue