commit 6b5a24152e26da1ebd1fc3e08bd49c7f06b7e283 Author: Skylar Ittner Date: Sat Apr 27 18:58:44 2019 -0600 Create POI API and importer script diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..87dc92a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/settings.php +/database.mwb.bak +/vendor/ +/nbproject/private \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..d13ac31 --- /dev/null +++ b/composer.json @@ -0,0 +1,16 @@ +{ + "name": "netsyms/points-of-interest-api", + "description": "Simple PHP API for getting OpenStreetMap points of interest as GeoJSON", + "type": "project", + "require": { + "catfan/medoo": "^1.6", + "anthonymartin/geo-location": "^1.0" + }, + "license": "MIT", + "authors": [ + { + "name": "Skylar Ittner", + "email": "admin@netsyms.com" + } + ] +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..da8bd82 --- /dev/null +++ b/composer.lock @@ -0,0 +1,122 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "content-hash": "7717ebd7c56c3d078f88c2c9aab56f61", + "packages": [ + { + "name": "anthonymartin/geo-location", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/anthonymartin/GeoLocation.php.git", + "reference": "50bf026f069296dfae11aa195d987854b2e75855" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/anthonymartin/GeoLocation.php/zipball/50bf026f069296dfae11aa195d987854b2e75855", + "reference": "50bf026f069296dfae11aa195d987854b2e75855", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "class", + "autoload": { + "psr-0": { + "AnthonyMartin": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "CC 3.0" + ], + "authors": [ + { + "name": "Anthony Martin", + "email": "anthony@replaycreative.com", + "homepage": "http://replaycreative.com", + "role": "Developer" + } + ], + "description": "Retrieve bounding coordinates, distances, longitude and latitude with GeoLocation.class.php", + "homepage": "https://github.com/anthonymartin/GeoLocation.php", + "keywords": [ + "bounding coordinates", + "distances", + "geocoding", + "geolocation" + ], + "time": "2016-09-17T18:05:14+00:00" + }, + { + "name": "catfan/medoo", + "version": "v1.6.1", + "source": { + "type": "git", + "url": "https://github.com/catfan/Medoo.git", + "reference": "53a02b300d673f716cb06bf0e24fd774ec53939f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/catfan/Medoo/zipball/53a02b300d673f716cb06bf0e24fd774ec53939f", + "reference": "53a02b300d673f716cb06bf0e24fd774ec53939f", + "shasum": "" + }, + "require": { + "ext-pdo": "*", + "php": ">=5.4" + }, + "suggest": { + "ext-pdo_dblib": "For MSSQL or Sybase database on Linux/UNIX platform", + "ext-pdo_mysql": "For MySQL or MariaDB database", + "ext-pdo_oci": "For Oracle database", + "ext-pdo_oci8": "For Oracle version 8 database", + "ext-pdo_pqsql": "For PostgreSQL database", + "ext-pdo_sqlite": "For SQLite database", + "ext-pdo_sqlsrv": "For MSSQL database" + }, + "type": "framework", + "autoload": { + "psr-4": { + "Medoo\\": "/src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Angel Lai", + "email": "angel@catfan.me" + } + ], + "description": "The lightest PHP database framework to accelerate development", + "homepage": "https://medoo.in", + "keywords": [ + "database", + "lightweight", + "mariadb", + "mssql", + "mysql", + "oracle", + "php framework", + "postgresql", + "sql", + "sqlite" + ], + "time": "2018-12-08T20:24:23+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/database.mwb b/database.mwb new file mode 100644 index 0000000..247d5ab Binary files /dev/null and b/database.mwb differ diff --git a/import_csv.php b/import_csv.php new file mode 100644 index 0000000..49437af --- /dev/null +++ b/import_csv.php @@ -0,0 +1,90 @@ + $SETTINGS['database']['type'], + 'database_name' => $SETTINGS['database']['name'], + 'server' => $SETTINGS['database']['server'], + 'username' => $SETTINGS['database']['user'], + 'password' => $SETTINGS['database']['password'], + 'charset' => $SETTINGS['database']['charset'] + ]); +} catch (Exception $ex) { + die("Database error: $ex\n"); +} + + + +if (!file_exists($argv[1])) { + die("Please supply the filename of the CSV file to import.\n"); +} + +$current = 0; + +$handle = fopen($argv[1], "r"); +if ($handle) { + $batchinsert = []; + while (($line = fgets($handle)) !== false) { + $fields = explode("|", trim($line), 5); + + $batchinsert[] = [ + "osmid" => substr($fields[1], 1), + "typeid" => $fields[0], + "latitude" => $fields[2], + "longitude" => $fields[3], + "eletype" => $fields[1][0], + "name" => $fields[4] + ]; + + $current++; + if ($current % 100 == 0) { + $database->insert("poi", $batchinsert); + $error = $database->error(); + if ($error[1] != 0 && $error[0] != "23000") { + die("\nDatabase error $error[0]: $error[2]\n"); + } + $batchinsert = []; + echo " $current "; + } + } + + fclose($handle); + + echo "\nDone: $current records processed.\n"; +} else { + die("Error: could not open the file.\n"); +} \ No newline at end of file diff --git a/index.php b/index.php new file mode 100644 index 0000000..970b8d0 --- /dev/null +++ b/index.php @@ -0,0 +1,138 @@ + "OK" + ]; + // If the data is an array, make a FeatureCollection + // If there's no results, make an empty FeatureCollection + if (count($data) == 0 || is_array($data[0])) { + $geojson["type"] = "FeatureCollection"; + $geojson["results"] = count($data); + $geojson["max_results"] = $SETTINGS["max_results"]; + $geojson["features"] = []; + + foreach ($data as $item) { + $geojson["features"][] = [ + "type" => "Feature", + "geometry" => [ + "type" => "Point", + "coordinates" => [ + $item["longitude"] * 1.0, + $item["latitude"] * 1.0 + ] + ], + "properties" => [ + "osmid" => $item["osmid"] * 1, + "name" => $item["name"], + "typeid" => $item["typeid"] * 1, + "type" => $item["type"], + "eletype" => $item["eletype"] + ] + ]; + } + } else { + $geojson["type"] = "Feature"; + $geojson["geometry"] = [ + "type" => "Point", + "coordinates" => [ + $data["longitude"] * 1.0, + $data["latitude"] * 1.0 + ] + ]; + $geojson["properties"] = [ + "osmid" => $data["osmid"] * 1, + "name" => $data["name"], + "typeid" => $data["typeid"] * 1, + "type" => $data["type"], + "eletype" => $data["eletype"] + ]; + } + + return $geojson; +} + +$where = []; + +$validations = [ + "osmid" => "/[0-9]+/", + "typeid" => "/[1-9]+/", + "type" => "/[A-Z]+_[A-Z]+", + "latitude" => "/[0-9]{0,3}\.[0-9]{2,10}/", + "longitude" => "/[0-9]{0,3}\.[0-9]{2,10}/", + "radius_km" => "/[0-9]+/", + "radius_mi" => "/[0-9]+/" +]; + +foreach ($validations as $name => $regex) { + if (!empty($_GET[$name])) { + if (is_array($_GET[$name])) { + foreach ($_GET[$name] as $t) { + if (!preg_match($regex, $t)) { + exit("[]"); + } + } + } else { + if (!preg_match($regex, $_GET[$name])) { + exit("[]"); + } + } + if (preg_match("/(osmid|typeid|type)/", $name)) { + $where[$name] = $_GET[$name]; + } + } +} + +// Calculate bounding box +use AnthonyMartin\GeoLocation\GeoLocation as GeoLocation; + +if (!empty($_GET["latitude"]) && !empty($_GET["longitude"])) { + + $userlocation = GeoLocation::fromDegrees($_GET["latitude"], $_GET["longitude"]); + + $radius = $SETTINGS["default_radius"]; + if (!empty($_GET["radius_km"])) { + $radius = $_GET["radius_km"] * 1.0; + } else if (!empty($_GET["radius_mi"])) { + $radius = $_GET["radius_mi"] / 0.62137; + } + $searchbounds = $userlocation->boundingCoordinates($radius, "kilometers"); + + $where["latitude[<>]"] = [$searchbounds[0]->getLatitudeInDegrees(), $searchbounds[1]->getLatitudeInDegrees()]; + $where["longitude[<>]"] = [$searchbounds[0]->getLongitudeInDegrees(), $searchbounds[1]->getLongitudeInDegrees()]; +} + +// Remove database ambiguity +foreach ($where as $key => $value) { + if ($key == "typeid") { + $where["poi.typeid"] = $value; + unset($where["typeid"]); + } +} + +if (count($where) > 1) { + $where = ["AND" => $where]; +} + +$where["LIMIT"] = $SETTINGS["max_results"]; + + +ob_flush(); +$database->debug()->select("poi", ["[>]types" => "typeid"], ["osmid", "poi.typeid", "type", "latitude", "longitude", "eletype", "name"], $where); +$query = ob_get_contents(); +ob_clean(); +$results = $database->query($query)->fetchAll(); + +$geojson = getgeojson($results); + +if ($SETTINGS["debug"]) { + $geojson["query"] = $query; +} + +if (empty($_GET["pretty"])) { + exit(json_encode($geojson)); +} +exit(json_encode($geojson, JSON_PRETTY_PRINT)); diff --git a/nbproject/project.properties b/nbproject/project.properties new file mode 100644 index 0000000..67f7bd3 --- /dev/null +++ b/nbproject/project.properties @@ -0,0 +1,7 @@ +include.path=${php.global.include.path} +php.version=PHP_72 +source.encoding=UTF-8 +src.dir=. +tags.asp=false +tags.short=false +web.root=. diff --git a/nbproject/project.xml b/nbproject/project.xml new file mode 100644 index 0000000..93b156d --- /dev/null +++ b/nbproject/project.xml @@ -0,0 +1,9 @@ + + + org.netbeans.modules.php.project + + + PointsOfInterestAPI + + + diff --git a/poi-types.csv b/poi-types.csv new file mode 100644 index 0000000..ea3399a --- /dev/null +++ b/poi-types.csv @@ -0,0 +1,167 @@ +1,ACCOMMO_ALPINEHUT +2,ACCOMMO_CAMPING +3,ACCOMMO_CARAVAN +4,ACCOMMO_CHALET +5,ACCOMMO_HOSTEL +6,ACCOMMO_HOTEL +7,ACCOMMO_MOTEL +8,AMENITY_COURT +9,AMENITY_FIRESTATION +11,AMENITY_LIBRARY +12,AMENITY_PLAYGROUND +13,AMENITY_POLICE +14,AMENITY_POSTOFFICE +15,AMENITY_PRISON +16,AMENITY_PUBLICBUILDING +17,AMENITY_TOWNHALL +18,BARRIER_BLOCKS +19,EDUCATION_COLLEGE +20,EDUCATION_NURSERY +21,EDUCATION_SCHOOL +22,EDUCATION_UNIVERSITY +23,FOOD_BAR +24,FOOD_BIERGARTEN +25,FOOD_CAFE +26,FOOD_FASTFOOD +27,FOOD_ICECREAM +28,FOOD_PUB +29,FOOD_RESTAURANT +30,HEALTH_DENTIST +31,HEALTH_DOCTORS +32,HEALTH_HOSPITALEMERGENCY +33,HEALTH_HOSPITAL +34,HEALTH_PHARMACY +35,HEALTH_VETERINARY +36,LANDUSE_ALLOTMENTS +37,LANDUSE_CONIFEROUSDECIDUOUS +38,LANDUSE_CONIFEROUS +39,LANDUSE_DECIDUOUS +40,LANDUSE_GRASS +41,LANDUSE_HILLS +42,LANDUSE_MILITARY +43,LANDUSE_QUARY +44,LANDUSE_SCRUB +45,LANDUSE_SWAMP +46,MONEY_BANK +47,MONEY_EXCHANGE +48,POW_BAHAI +49,POW_BUDDHIST +50,POW_CHRISTIAN +51,POW_HINDU +52,POW_ISLAMIC +53,POW_JAIN +54,POW_JEWISH +55,POW_SHINTO +56,POW_SIKH +58,POW_UNKOWN +59,POI_CAVE +60,POI_CRANE +61,POI_EMBASSY +62,POI_BUNKER +63,POI_MINE +64,POI_PEAK1 +65,POI_PEAK +66,POI_CITY +67,POI_HAMLET +68,POI_SUBURB +69,POI_TOWN +70,POI_VILLAGE +71,POI_TOWERCOMMUNICATION +72,POI_TOWERLOOKOUT +73,SHOP_ALCOHOL +74,SHOP_BAKERY +75,SHOP_BICYCLE +76,SHOP_BOOK +77,SHOP_BUTCHER +78,SHOP_CARREPAIR +79,SHOP_CAR +80,SHOP_CLOTHES +81,SHOP_COMPUTER +82,SHOP_CONFECTIONERY +83,SHOP_CONVENIENCE +84,SHOP_COPYSHOP +85,SHOP_DEPARTMENTSTORE +86,SHOP_DIY +87,SHOP_FISH +88,SHOP_FLORIST +89,SHOP_GARDENCENTRE +90,SHOP_GIFT +91,SHOP_GREENGROCER +92,SHOP_HAIRDRESSER +93,SHOP_HEARINGAIDS +94,SHOP_HIFI +95,SHOP_JEWELRY +96,SHOP_KIOSK +97,SHOP_LAUNDRETTE +98,SHOP_MARKETPLACE +99,SHOP_PHONE +100,SHOP_MOTORCYCLE +101,SHOP_MUSIC +102,SHOP_NEWSPAPER +103,SHOP_PET +104,SHOP_SHOES +105,SHOP_SUPERMARKET +106,SHOP_TOBACCO +107,SHOP_TOYS +108,SHOP_VENDINGMASCHINE +109,SHOP_VIDEORENTAL +110,SPORT_ARCHERY +111,SPORT_BASEBALL +112,SPORT_BASKETBALL +113,SPORT_BOWLING +114,SPORT_CANOE +115,SPORT_CRICKET +116,SPORT_DIVING +117,SPORT_FOOTBALL +118,SPORT_GOLF +119,SPORT_GYM +120,SPORT_GYMNASIUM +121,SPORT_CLIMBING +122,SPORT_HORSE +123,SPORT_ICESKATING +124,SPORT_LEISURECENTER +125,SPORT_MINIATURGOLF +126,SPORT_MOTORRACING +127,SPORT_SHOOTING +128,SPORT_SKATING +129,SPORT_SKIINGDOWNHILL +130,SPORT_SNOOKER +131,SPORT_SOCCER +132,SPORT_STADIUM +133,SPORT_SWIMMING +134,SPORT_TENNIS +135,SPORT_WATERSKI +136,SPORT_SURFING +137,TOURIST_ARCHAELOGICAL +138,TOURIST_ART +139,TOURIST_ATTRACTION +140,TOURIST_BATTLEFIELD +141,TOURIST_BEACH +142,TOURIST_CASTLE +143,TOURIST_CASTLE2 +144,TOURIST_CINEMA +145,TOURIST_FOUNTAIN +146,TOURIST_INFORMATION +147,TOURIST_MEMORIAL +148,TOURIST_MONUMENT +149,TOURIST_MUSEUM +150,TOURIST_NIGHTCLUB +151,TOURIST_RUINS +152,TOURIST_THEATRE +153,TOURIST_THEMEPARK +156,TOURIST_WINDMILL +157,TOURIST_WRECK +158,TOURIST_ZOO +159,TRANSPORT_TERMINAL +160,TRANSPORT_AIRPORT +161,TRANSPORT_BUSSTOP +162,TRANSPORT_FUEL +163,TRANSPORT_LIGHTHOUSE +164,TRANSPORT_MARINA +165,TRANSPORT_RENTALCAR +166,TRANSPORT_SUBWAY +167,TRANSPORT_STATION +168,TRANSPORT_TRAMSTOP +169,WATER_DAM +170,WATER_TOWER +171,WATER_WEIR diff --git a/required.php b/required.php new file mode 100644 index 0000000..650544c --- /dev/null +++ b/required.php @@ -0,0 +1,80 @@ + "ERROR", "message" => $msg])); +} + +// Database settings +// Also inits database and stuff +use Medoo\Medoo; + +$database; +try { + $database = new Medoo([ + 'database_type' => $SETTINGS['database']['type'], + 'database_name' => $SETTINGS['database']['name'], + 'server' => $SETTINGS['database']['server'], + 'username' => $SETTINGS['database']['user'], + 'password' => $SETTINGS['database']['password'], + 'charset' => $SETTINGS['database']['charset'] + ]); +} catch (Exception $ex) { + sendError("Database error. Try again later. $ex"); +} + + +if (!$SETTINGS['debug']) { + error_reporting(0); +} else { + error_reporting(E_ALL); + ini_set('display_errors', 'On'); +} + + +$VARS; +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $VARS = $_POST; + define("GET", false); +} else { + $VARS = $_GET; + define("GET", true); +} + + +/** + * Check if the previous database action had a problem. + * @param array $specials int=>string array with special response messages for SQL errors + */ +function checkDBError($specials = []) { + global $database; + $errors = $database->error(); + if (!is_null($errors[1])) { + foreach ($specials as $code => $text) { + if ($errors[1] == $code) { + sendError($text); + } + } + sendError("A database error occurred: " . $errors[2]); + } +} diff --git a/settings.template.php b/settings.template.php new file mode 100644 index 0000000..40074fb --- /dev/null +++ b/settings.template.php @@ -0,0 +1,36 @@ + false, + // Database connection settings + // See http://medoo.in/api/new for info + "database" => [ + "type" => "mysql", + "name" => "poidb", + "server" => "localhost", + "user" => "root", + "password" => "", + "charset" => "utf8" + ], + // Maximum number of results to return. + "max_results" => 1000, + // Default radius when searching by location, in kilometers + "default_radius" => 10, + // Name of the app. + "site_title" => "OSM Points of Interest", + // For supported values, see http://php.net/manual/en/timezones.php + "timezone" => "America/Denver" +];