- Notifications
You must be signed in to change notification settings - Fork126
Geo-related tools PHP 7.3+ library built atop Geocoder and React libraries
License
thephpleague/geotools
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Geotools is a PHP geo-related library, built atopGeocoder andReact libraries.
- Batch geocode & reverse geocoding request(s) inseries / inparallel against one or aset of providers.»
- Cache geocode & reverse geocoding result(s) withPSR-6 to improve performances.»
- Compute geocode & reverse geocoding in thecommand-line interface (CLI) + dumpers and formatters.»
- Acceptalmost all kind of WGS84geographic coordinates as coordinates.»
- Support23 different ellipsoids and it's easy to provide a new one if needed.»
- Convert andformat decimal degrees coordinates to decimal minutes or degrees minutes seconds coordinates.»
- Convert decimal degrees coordinates in theUniversal Transverse Mercator(UTM) projection.»
- Compute the distance inmeter (by default),km,mi orft between two coordinates usingflat,great circle,haversine orvincenty algorithms.»
- Compute the initial and finalbearing from the origin coordinate to the destination coordinate in degrees.»
- Compute the initial and finalcardinal point (direction) from the origin coordinate to the destinationcoordinate, read more inwikipedia.»
- Compute thehalf-way point (coordinate) between the origin and the destination coordinates.»
- Compute thedestination point (coordinate) with given bearing in degrees and a distance in meters.»
- Encode a coordinate to ageo hash string and decode it to a coordinate, read more inwikipedia and ongeohash.org.»
- Encode a coordinate via the 10:10 algorithm.»
- Polygon class provides methods to check either a poing (coordinate) is in, or on the polygon's boundaries.»
- Acommand-line interface (CLI) forDistance,Point,Geohash andConvert classes.»
- Integration with Frameworks:Laravel 4,Silex ...»
- ... more to come ...
Geotools can be found onPackagist.The recommended way to installGeotools is throughcomposer.
Run the following on the command line:
composer require league/geotoolsImportant: you should use the0.4 version if you use Geocoder2.x or/and PHP5.3.
And install dependencies:
composer installNow you can add the autoloader, and you will have access to the library:
<?phprequire'vendor/autoload.php';
The default geodetic datum isWGS84 and coordinates are indecimal degrees.
Here are the available ellipsoids:AIRY,AUSTRALIAN_NATIONAL,BESSEL_1841,BESSEL_1841_NAMBIA,CLARKE_1866,CLARKE_1880,EVEREST,FISCHER_1960_MERCURY,FISCHER_1968,GRS_1967,GRS_1980,HELMERT_1906,HOUGH,INTERNATIONAL,KRASSOVSKY,MODIFIED_AIRY,MODIFIED_EVEREST,MODIFIED_FISCHER_1960,SOUTH_AMERICAN_1969,WGS60,WGS66,WGS72, andWGS84.
If you need to use an other ellipsoid, just create an array like this:
<?php$myEllipsoid = \League\Geotools\Coordinate\Ellipsoid::createFromArray(['name' =>'My Ellipsoid',// The name of the Ellipsoid'a' =>123.0,// The semi-major axis (equatorial radius) in meters'invF' =>456.0// The inverse flattening]);
Geotools is built atopGeocoder. It means it's possible to use the\Geocoder\Model\Address directly but it's also possible to use astring or a simplearray with itslatitude and longitude.
It supportsvalid and acceptable geographic coordinateslike:
- 40:26:46N,079:56:55W
- 40:26:46.302N 079:56:55.903W
- 40°26′47″N 079°58′36″W
- 40d 26′ 47″ N 079d 58′ 36″ W
- 40.446195N 79.948862W
- 40.446195, -79.948862
- 40° 26.7717, -79° 56.93172
Latitudes below -90.0 or above 90.0 degrees arecapped through\League\Geotools\Coordinate\Coordinate::normalizeLatitude().Longitudes below -180.0 or above 180.0 degrees arewrapped through\League\Geotools\Coordinate\Coordinate::normalizeLongitude().
<?phpuseLeague\Geotools\Coordinate\Coordinate;useLeague\Geotools\Coordinate\Ellipsoid;// from an \Geocoder\Model\Address instance within Airy ellipsoid$coordinate =newCoordinate($geocoderResult, Ellipsoid::createFromName(Ellipsoid::AIRY));// or in an array of latitude/longitude coordinate within GRS 1980 ellipsoid$coordinate =newCoordinate([48.8234055,2.3072664], Ellipsoid::createFromName(Ellipsoid::GRS_1980));// or in latitude/longitude coordinate within WGS84 ellipsoid$coordinate =newCoordinate('48.8234055, 2.3072664');// or in degrees minutes seconds coordinate within WGS84 ellipsoid$coordinate =newCoordinate('48°49′24″N, 2°18′26″E');// or in decimal minutes coordinate within WGS84 ellipsoid$coordinate =newCoordinate('48 49.4N, 2 18.43333E');// the result will be:printf("Latitude: %F\n",$coordinate->getLatitude());// 48.8234055printf("Longitude: %F\n",$coordinate->getLongitude());// 2.3072664printf("Ellipsoid name: %s\n",$coordinate->getEllipsoid()->getName());// WGS 84printf("Equatorial radius: %F\n",$coordinate->getEllipsoid()->getA());// 6378136.0printf("Polar distance: %F\n",$coordinate->getEllipsoid()->getB());// 6356751.317598printf("Inverse flattening: %F\n",$coordinate->getEllipsoid()->getInvF());// 298.257224printf("Mean radius: %F\n",$coordinate->getEllipsoid()->getArithmeticMeanRadius());// 6371007.772533// it's also possible to modify the coordinate without creating an other coodinate$coordinate->setFromString('40°26′47″N 079°58′36″W');printf("Latitude: %F\n",$coordinate->getLatitude());// 40.446388888889printf("Longitude: %F\n",$coordinate->getLongitude());// -79.976666666667
It provides methods (and aliases) to convertdecimal degrees WGS84 coordinates todegrees minutes secondsordecimal minutes WGS84 coordinates. You can format the output string easily.
You can also convert them in the Universal Transverse Mercator (UTM) projection (Southwest coast of Norway and theregion of Svalbard are covered).
<?php$geotools =new \League\Geotools\Geotools();$coordinate =new \League\Geotools\Coordinate\Coordinate('40.446195, -79.948862');$converted =$geotools->convert($coordinate);// convert to decimal degrees without and with format stringprintf("%s\n",$converted->toDecimalMinutes());// 40 26.7717N, -79 56.93172W// convert to degrees minutes seconds without and with format stringprintf("%s\n",$converted->toDegreesMinutesSeconds('<p>%P%D:%M:%S, %p%d:%m:%s</p>'));// <p>40:26:46, -79:56:56</p>// convert in the UTM projection (standard format)printf("%s\n",$converted->toUniversalTransverseMercator());// 17T 589138 4477813
Here is the mapping:
| Decimal minutes | Latitude | Longitude |
|---|---|---|
| Positive or negative sign | %P | %p |
| Direction | %L | %l |
| Degrees | %D | %d |
| Decimal minutes | %N | %n |
| Degrees minutes seconds | Latitude | Longitude |
|---|---|---|
| Positive or negative sign | %P | %p |
| Direction | %L | %l |
| Degrees | %D | %d |
| Minutes | %M | %m |
| Seconds | %S | %s |
It provides a very handy way to batch geocode and reverse geocoding requests inserie or inparallel againsta set of providers.Thanks toGeocoder andReact libraries.
It's possible to batchone request (a string) or aset of request (an array) againstone provider orset of providers.
You can use a providedcache engine or use your own by setting a cache object which should implementLeague\Geotools\Cache\CacheInterface and extendLeague\Geotools\Cache\AbstractCache if needed.
At the moment Geotools supports any PSR-6 cache.
NB: Before you implement caching in your app please be sure that doing so does not violate the Terms of Servicefor your(s) geocoding provider(s).
<?php$geocoder =new \Geocoder\ProviderAggregator();// or \Geocoder\TimedGeocoder$httpClient = HttpClientDiscovery::find();$geocoder->registerProviders([new \Geocoder\Provider\GoogleMaps\GoogleMaps($httpClient),new \Geocoder\Provider\OpenStreetMap\OpenStreetMap($httpClient),new \Geocoder\Provider\BingMaps\BingMaps($httpClient,'<FAKE_API_KEY>'),// throws InvalidCredentialsExceptionnew \Geocoder\Provider\Yandex\Yandex($httpClient),new \Geocoder\Provider\FreeGeoIp\FreeGeoIp($httpClient),new \Geocoder\Provider\Geoip\Geoip(),]);try {$geotools =new \League\Geotools\Geotools();$cache =new \Cache\Adapter\PHPArray\ArrayCachePool();$results =$geotools->batch($geocoder)->setCache($cache)->geocode(['Paris, France','Copenhagen, Denmark','74.200.247.59','::ffff:66.147.244.214' ])->parallel();}catch (\Exception$e) {die($e->getMessage());}$dumper =new \Geocoder\Dumper\WktDumper();foreach ($resultsas$result) {// if a provider throws an exception (UnsupportedException, InvalidCredentialsException ...)// an custom /Geocoder/Result/Geocoded instance is returned which embedded the name of the provider,// the query string and the exception string. It's possible to use dumpers// and/or formatters from the Geocoder library.printf("%s|%s|%s\n",$result->getProviderName(),$result->getQuery(),'' ==$result->getExceptionMessage() ?$dumper->dump($result) :$result->getExceptionMessage() );}
You should get 24 results (4 values to geocode against 6 providers) something like:
google_maps|Paris, France|POINT(2.352222 48.856614)google_maps|Copenhagen, Denmark|POINT(12.568337 55.676097)google_maps|74.200.247.59|The GoogleMapsProvider does not support IP addresses.google_maps|::ffff:66.147.244.214|The GoogleMapsProvider does not support IP addresses.openstreetmap|Paris, France|POINT(2.352133 48.856506)openstreetmap|Copenhagen, Denmark|POINT(12.570072 55.686724)openstreetmap|74.200.247.59|Could not execute query http://nominatim.openstreetmap.org/search?q=74.200.247.59&format=xml&addressdetails=1&limit=1openstreetmap|::ffff:66.147.244.214|The OpenStreetMapProvider does not support IPv6 addresses.bing_maps|Paris, France|Could not execute query http://dev.virtualearth.net/REST/v1/Locations/?q=Paris%2C+France&key=<FAKE_API_KEY>bing_maps|Copenhagen, Denmark|Could not execute query http://dev.virtualearth.net/REST/v1/Locations/?q=Copenhagen%2C+Denmark&key=<FAKE_API_KEY>bing_maps|74.200.247.59|The BingMapsProvider does not support IP addresses.bing_maps|::ffff:66.147.244.214|The BingMapsProvider does not support IP addresses.yandex|Paris, France|POINT(2.341198 48.856929)yandex|Copenhagen, Denmark|POINT(12.567602 55.675682)yandex|74.200.247.59|The YandexProvider does not support IP addresses.yandex|::ffff:66.147.244.214|The YandexProvider does not support IP addresses.free_geo_ip|Paris, France|The FreeGeoIpProvider does not support Street addresses.free_geo_ip|Copenhagen, Denmark|The FreeGeoIpProvider does not support Street addresses.free_geo_ip|74.200.247.59|POINT(-122.415600 37.748400)free_geo_ip|::ffff:66.147.244.214|POINT(-111.613300 40.218100)geoip|Paris, France|The GeoipProvider does not support Street addresses.geoip|Copenhagen, Denmark|The GeoipProvider does not support Street addresses.geoip|74.200.247.59|POINT(-122.415604 37.748402)geoip|::ffff:66.147.244.214|The GeoipProvider does not support IPv6 addresses.Batch reverse geocoding is something like:
<?php// ... $geocoder like the previous example ...// If you want to reverse one coordinatetry {$results =$geotools->batch($geocoder)->reverse(new \League\Geotools\Coordinate\Coordinate([2.307266,48.823405]) )->parallel();}catch (\Exception$e) {die($e->getMessage());}// Or if you want to reverse geocoding 3 coordinates$coordinates = [new \League\Geotools\Coordinate\Coordinate([2.307266,48.823405]),new \League\Geotools\Coordinate\Coordinate([12.568337,55.676097]),new \League\Geotools\Coordinate\Coordinate('-74.005973 40.714353')),];$results =$geotools->batch($geocoder)->reverse($coordinates)->parallel();// ...
If you want to batch it in serie, replace the methodparallel() byserie().
To optimize batch requests you need to register providers according to theircapabilities and what you'relooking for (geocode street addresses, geocode IPv4, geocode IPv6 or reverse geocoding),please read more at theGeocoder library doc.
It provides methods to compute the distance inmeter (by default),km,mi orft between two coordinatesusingflat (most performant),great circle,haversine orvincenty (most accurate) algorithms.
Those coordinates should be in the same ellipsoid.
<?php$geotools =new \League\Geotools\Geotools();$coordA =new \League\Geotools\Coordinate\Coordinate([48.8234055,2.3072664]);$coordB =new \League\Geotools\Coordinate\Coordinate([43.296482,5.36978]);$distance =$geotools->distance()->setFrom($coordA)->setTo($coordB);printf("%s\n",$distance->flat());// 659166.50038742 (meters)printf("%s\n",$distance->greatCircle());// 659021.90812846printf("%s\n",$distance->in('km')->haversine());// 659.02190812846printf("%s\n",$distance->in('mi')->vincenty());// 409.05330679648printf("%s\n",$distance->in('ft')->flat());// 2162619.7519272
It provides methods to compute the initial and finalbearing in degrees, the initial and finalcardinal direction,themiddle point and thedestination point. The middle and the destination points returns a\League\Geotools\Coordinate\Coordinate object with the same ellipsoid.
<?php$geotools =new \League\Geotools\Geotools();$coordA =new \League\Geotools\Coordinate\Coordinate([48.8234055,2.3072664]);$coordB =new \League\Geotools\Coordinate\Coordinate([43.296482,5.36978]);$vertex =$geotools->vertex()->setFrom($coordA)->setTo($coordB);printf("%d\n",$vertex->initialBearing());// 157 (degrees)printf("%s\n",$vertex->initialCardinal());// SSE (SouthSouthEast)printf("%d\n",$vertex->finalBearing());// 160 (degrees)printf("%s\n",$vertex->finalCardinal());// SSE (SouthSouthEast)$middlePoint =$vertex->middle();// \League\Geotools\Coordinate\Coordinateprintf("%s\n",$middlePoint->getLatitude());// 46.070143125815printf("%s\n",$middlePoint->getLongitude());// 3.9152401085931$destinationPoint =$geotools->vertex()->setFrom($coordA)->destination(180,200000);// \League\Geotools\Coordinate\Coordinateprintf("%s\n",$destinationPoint->getLatitude());// 47.026774650075printf("%s\n",$destinationPoint->getLongitude());// 2.3072664
It provides methods to get thegeo hash and itsbounding box's coordinates (SouthWest & NorthEast)of a coordinate and thecoordinate and itsbounding box's coordinates (SouthWest & NorthEast) of a geo hash.
<?php$geotools =new \League\Geotools\Geotools();$coordToGeohash =new \League\Geotools\Coordinate\Coordinate('43.296482, 5.36978');// encoding$encoded =$geotools->geohash()->encode($coordToGeohash,4);// 12 is the default length / precision// encodedprintf("%s\n",$encoded->getGeohash());// spey// encoded bounding box$boundingBox =$encoded->getBoundingBox();// array of \League\Geotools\Coordinate\CoordinateInterface$southWest =$boundingBox[0];$northEast =$boundingBox[1];printf("http://www.openstreetmap.org/?minlon=%s&minlat=%s&maxlon=%s&maxlat=%s&box=yes\n",$southWest->getLongitude(),$southWest->getLatitude(),$northEast->getLongitude(),$northEast->getLatitude());// http://www.openstreetmap.org/?minlon=5.2734375&minlat=43.2421875&maxlon=5.625&maxlat=43.41796875&box=yes// decoding$decoded =$geotools->geohash()->decode('spey61y');// decoded coordinateprintf("%s\n",$decoded->getCoordinate()->getLatitude());// 43.296432495117printf("%s\n",$decoded->getCoordinate()->getLongitude());// 5.3702545166016// decoded bounding box$boundingBox =$decoded->getBoundingBox();//array of \League\Geotools\Coordinate\CoordinateInterface$southWest =$boundingBox[0];$northEast =$boundingBox[1];printf("http://www.openstreetmap.org/?minlon=%s&minlat=%s&maxlon=%s&maxlat=%s&box=yes\n",$southWest->getLongitude(),$southWest->getLatitude(),$northEast->getLongitude(),$northEast->getLatitude());// http://www.openstreetmap.org/?minlon=5.3695678710938&minlat=43.295745849609&maxlon=5.3709411621094&maxlat=43.297119140625&box=yes
You can also get information about neighbor points (image).
<?php$geotools =new \League\Geotools\Geotools();// decoding$decoded =$geotools->geohash()->decode('spey61y');// get neighbor geohashprintf("%s\n",$decoded->getNeighbor(\League\Geotools\Geohash\Geohash::DIRECTION_NORTH));// spey64nprintf("%s\n",$decoded->getNeighbor(\League\Geotools\Geohash\Geohash::DIRECTION_SOUTH_EAST));// spey61x// get all neighbor geohashesprint_r($decoded->getNeighbors(true));/** * Array * ( * [north] => spey64n * [south] => spey61w * [west] => spey61v * [east] => spey61z * [north_west] => spey64j * [north_east] => spey64p * [south_west] => spey61t * [south_east] => spey61x * ) */
Represent a location with 10m accuracy using a 10 character code that includes features to prevent errors inentering the code. Read more about the algorithmhere.
<?php$tenten =new \League\Geotools\Tests\Geohash\TenTen;$tenten->encode(newCoordinate([51.09559,1.12207]));// MEQ N6G 7NY5
Represents a segment with a direction.You can find if two vertexes are on the same line.
<?php$vertexA->setFrom(48.8234055);$vertexA->setTo(2.3072664);$vertexB->setFrom(48.8234055);$vertexB->setTo(2.3072664);$vertexA->isOnSameLine($vertexB);
It helps you to know if a point (coordinate) is in a Polygon or on the Polygon's boundaries and if this in ona Polygon's vertex.
First you need to create the polygon, you can provide:
- an array of arrays
- an array of
Coordinate - a
CoordinateCollection
<?php$polygon =new \League\Geotools\Polygon\Polygon([ [48.9675969,1.7440796], [48.4711003,2.5268555], [48.9279131,3.1448364], [49.3895245,2.6119995],]);$polygon->setPrecision(5);// set the comparision precision$polygon->pointInPolygon(new \League\Geotools\Coordinate\Coordinate([49.1785607,2.4444580]));// true$polygon->pointInPolygon(new \League\Geotools\Coordinate\Coordinate([49.1785607,5]));// false$polygon->pointOnBoundary(new \League\Geotools\Coordinate\Coordinate([48.7193486,2.13546755]));// true$polygon->pointOnBoundary(new \League\Geotools\Coordinate\Coordinate([47.1587188,2.87841795]));// false$polygon->pointOnVertex(new \League\Geotools\Coordinate\Coordinate([48.4711003,2.5268555]));// true$polygon->pointOnVertex(new \League\Geotools\Coordinate\Coordinate([49.1785607,2.4444580]));// false$polygon->getBoundingBox();// return the BoundingBox object
It provides command lines to compute methods provided byDistance,Point,Geohash andConvert classes.Thanks to theSymfony Console Component.
$ php geotools list // list of available commands$ php geotoolshelp distance:flat // get thehelp$ php geotools distance:flat"40° 26.7717, -79° 56.93172""30°16′57″N 029°48′32″W" // 4690203.1048522$ php geotools distance:haversine"35,45""45,35" --ft // 4593030.9787593$ php geotools distance:vincenty"35,45""45,35" --km // 1398.4080717661$ php geotools d:v"35,45""45,35" --km --ellipsoid=WGS60 // 1398.4145201642$ php geotools point:initial-cardinal"40:26:46.302N 079:56:55.903W""43.296482, 5.36978" // NE (NordEast)$ php geotools point:final-cardinal"40:26:46.302N 079:56:55.903W""43.296482, 5.36978" // ESE (EastSouthEast)$ php geotools point:destination"40° 26.7717, -79° 56.93172" 25 10000 // 40.527599285543, -79.898914904538$ php geotools p:d"40° 26.7717, -79° 56.93172" 25 10000 --ellipsoid=GRS_1980 // 40.527599272782, -79.898914912379$ php geotools geohash:encode"40° 26.7717, -79° 56.93172" --length=3 // dpp$ php geotools convert:dm"40.446195, -79.948862" --format="%P%D°%N %p%d°%n" // 40°26.7717 -79°56.93172$ php geotools convert:dms"40.446195, -79.948862" --format="%P%D:%M:%S, %p%d:%m:%s" // 40:26:46, -79:56:56$ php geotools convert:utm"60.3912628, 5.3220544" // 32V 297351 6700644$ php geotools c:u"60.3912628, 5.3220544" --ellipsoid=AIRY // 32V 297371 6700131...
Compute street addresses, IPv4s or IPv6s geocoding and reverse geocoding right in your console.
It's possible to define and precise your request through these options:
--provider:bing_maps,yahoo,maxmind...google_mapsis the default one. See the full listhere.--raw: the result output in RAW format, shows Adapter, Provider and Arguments if any.--json: the result output in JSON string format.--args: this option accepts multiple values (e.g. --args="API_KEY" --args="LOCALE") if your provider needs orcan have arguments.--dumper: this option is available for geocoding,gpx,geojson,kml,wkbandwktby default.Read morehere.--format: this option is available for reverse geocoding, see the mappinghere.
$ php geotoolshelp geocoder:geocode // get thehelp$ php geotools geocoder:geocode"Copenhagen, Denmark" // 55.6760968, 12.5683371$ php geotools geocoder:geocode"74.200.247.59" --provider="free_geo_ip" // 37.7484, -122.4156$ php geotools geocoder:geocode Paris --args="fr_FR" --args="France" --args="true" // 48.856614, 2.3522219$ php geotools geocoder:geocode Paris --dumper=wkt // POINT(2.352222 48.856614)...$ php geotools geocoder:reverse"48.8631507, 2.388911" // Avenue Gambetta 10, 75020 Paris$ php geotools geocoder:reverse"48.8631507, 2.388911" --format="%L, %A1, %C" // Paris, Île-De-France, France$ php geotools geocoder:reverse"48.8631507, 2.388911" --format="%L, %A1, %C" --provider="openstreetmap"// Paris, Île-De-France, France Métropolitaine...$ php geotools geocoder:geocode"Tagensvej 47, Copenhagen" --raw --args=da_DK --args=Denmark
The last command will show an output like this:
HttpClient: \Http\Client\Curl\ClientProvider: \Geocoder\Provider\GoogleMapsCache: \League\Geotools\Cache\RedisArguments: da_DK,Denmark---Latitude: 55.699953Longitude: 12.552736Bounds - South: 55.699953 - West: 12.552736 - North: 55.699953 - East: 12.552736Street Number: 47Street Name: TagensvejZipcode: 2200City: CopenhagenCity District: København NCounty: KøbenhavnCounty Code: KØBENHAVNRegion: Capital Region Of DenmarkRegion Code: CAPITAL REGION OF DENMARKCountry: DenmarkCountry Code: DKTimezone:To run unit tests, you'll need thecURL extension and a set of dependencies, you can install them using Composer:
$ php composer.phar install --dev
Once installed, just launch the following command:
$ phpunit --coverage-text
- Geocoder -MIT
- ReactPHP -MIT
- Symfony Console Component -MIT
- Symfony Serializer Component -MIT
- PHP client library for Redis -MIT
- Geokit,Geotools-for-CodeIgniter,geotools-php ...
Please seeCONTRIBUTING for details.
Bugs and feature request are tracked onGitHub
As contributors and maintainers of this project, we pledge to respect all peoplewho contribute through reporting issues, posting feature requests, updatingdocumentation, submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-freeexperience for everyone, regardless of level of experience, gender, genderidentity and expression, sexual orientation, disability, personal appearance,body size, race, age, or religion.
Examples of unacceptable behavior by participants include the use of sexuallanguage or imagery, derogatory comments or personal attacks, trolling, publicor private harassment, insults, or other unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or rejectcomments, commits, code, wiki edits, issues, and other contributions that arenot aligned to this Code of Conduct. Project maintainers who do not follow theCode of Conduct may be removed from the project team.
Instances of abusive, harassing, or otherwise unacceptable behavior may bereported by opening an issue or contacting one or more of the projectmaintainers.
This Code of Conduct is adapted from theContributorCovenant, version 1.0.0, available athttps://contributor-covenant.org/version/1/0/0/
Geotools is released under the MIT License. See the bundledLICENSE file for details.
About
Geo-related tools PHP 7.3+ library built atop Geocoder and React libraries
Topics
Resources
License
Contributing
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Uh oh!
There was an error while loading.Please reload this page.
