Skip to content

Instantly share code, notes, and snippets.

@lucien144
Last active January 9, 2020 16:12
Show Gist options
  • Save lucien144/0001b8d4fd743c5cebf2e5a916ee22d3 to your computer and use it in GitHub Desktop.
Save lucien144/0001b8d4fd743c5cebf2e5a916ee22d3 to your computer and use it in GitHub Desktop.
Class to convert pixels & GPS coords and back for PHP and DART
/**
* https://stackoverflow.com/a/41527934/4026345
*/
import 'dart:math';
import 'package:flutter/painting.dart';
class MercatorProjection {
final DEFAULT_PROJECTION_WIDTH = 256; // ignore: non_constant_identifier_names
final DEFAULT_PROJECTION_HEIGHT = 256; // ignore: non_constant_identifier_names
final double centerLatitude;
final double centerLongitude;
final int areaWidthPx;
final int areaHeightPx;
final double areaScale;
final double mapScale;
int _projectionWidth;
int _projectionHeight;
double _pixelsPerLonDegree;
double _pixelsPerLonRadian;
double _projectionCenterPx;
double _projectionCenterPy;
int get projectionWidth => _projectionWidth;
MercatorProjection({this.centerLatitude, this.centerLongitude, this.areaWidthPx, this.areaHeightPx, this.areaScale, this.mapScale = 1}) {
// TODO stretch the projection to match to deformity at the center lat/lon?
this._projectionWidth = this.DEFAULT_PROJECTION_WIDTH;
this._projectionHeight = this.DEFAULT_PROJECTION_HEIGHT;
this._pixelsPerLonDegree = this._projectionWidth / 360 * this.mapScale;
this._pixelsPerLonRadian = this._projectionWidth / ((2 / this.mapScale) * pi);
var centerPoint = this.projectLocation(this.centerLatitude, this.centerLongitude);
this._projectionCenterPx = centerPoint.x * this.areaScale;
this._projectionCenterPy = centerPoint.y * this.areaScale;
}
Point<double> projectLocation(double latitude, double longitude) {
var x = this._projectionWidth / 2 + longitude * this._pixelsPerLonDegree;
var siny = sin(this.deg2rad(latitude));
var y = this._projectionHeight / 2 + 0.5 * log((1 + siny) / (1 - siny)) * -this._pixelsPerLonRadian;
return Point<double>(x, y);
}
Offset getLocation(int px, int py) {
var x = this._projectionCenterPx + (px - this.areaWidthPx / 2);
var y = this._projectionCenterPy + (py - this.areaHeightPx / 2);
return this.projectPx(x / this.areaScale, y / this.areaScale);
}
Point<int> getPoint(double latitude, double longitude) {
var point = this.projectLocation(latitude, longitude);
var x = (point.x * this.areaScale - this._projectionCenterPx) + this.areaWidthPx / (2 / this.mapScale);
var y = (point.y * this.areaScale - this._projectionCenterPy) + this.areaHeightPx / (2 / this.mapScale);
return Point<int>(x.toInt(), y.toInt());
}
Offset projectPx(double px, double py) {
var longitude = (px - this._projectionWidth / 2) / this._pixelsPerLonDegree;
var latitudeRadians = (py - this._projectionHeight / 2) / -this._pixelsPerLonRadian;
var latitude = this.rad2deg(2 * atan(exp(latitudeRadians)) - pi / 2);
return Offset(latitude, longitude);
}
double deg2rad(double deg) {
return (deg * pi) / 180;
}
double rad2deg(double rad) {
return (rad * 180) / pi;
}
int get projectionHeight => _projectionHeight;
double get pixelsPerLonDegree => _pixelsPerLonDegree;
double get pixelsPerLonRadian => _pixelsPerLonRadian;
double get projectionCenterPx => _projectionCenterPx;
double get projectionCenterPy => _projectionCenterPy;
}
<?php declare(strict_types = 1);
namespace Projection;
use CropCount\Model\Pixel;
use CropCount\Model\Point;
/**
* https://stackoverflow.com/a/41527934/4026345
*
* Class MercatorProjection
*
* @package CropCount
*/
class MercatorProjection
{
private const DEFAULT_PROJECTION_WIDTH = 256;
private const DEFAULT_PROJECTION_HEIGHT = 256;
/**
* @var float
*/
private $centerLatitude;
/**
* @var float
*/
private $centerLongitude;
/**
* @var int
*/
private $areaWidthPx;
/**
* @var int
*/
private $areaHeightPx;
/**
* the scale that we would need for the a projection to fit the given area into a world view (1 = global, expect it to be > 1)
*
* @var float
*/
private $areaScale;
/**
* @var int
*/
private $mapScale;
/**
* @var int
*/
private $projectionWidth;
/**
* @var int
*/
private $projectionHeight;
/**
* @var float|int
*/
private $pixelsPerLonDegree;
/**
* @var float|int
*/
private $pixelsPerLonRadian;
/**
* @var float|int
*/
private $projectionCenterPx;
/**
* @var float|int
*/
private $projectionCenterPy;
public function __construct(float $centerLatitude, float $centerLongitude, int $areaWidthPx, int $areaHeightPx, float $areaScale, float $mapScale = 1)
{
$this->centerLatitude = $centerLatitude;
$this->centerLongitude = $centerLongitude;
$this->areaWidthPx = $areaWidthPx;
$this->areaHeightPx = $areaHeightPx;
$this->areaScale = $areaScale;
$this->mapScale = $mapScale;
// TODO stretch the projection to match to deformity at the center lat/lon?
$this->projectionWidth = self::DEFAULT_PROJECTION_WIDTH;
$this->projectionHeight = self::DEFAULT_PROJECTION_HEIGHT;
$this->pixelsPerLonDegree = $this->projectionWidth / 360 * $this->mapScale;
$this->pixelsPerLonRadian = $this->projectionWidth / ((2 / $this->mapScale) * \M_PI);
$centerPoint = $this->projectLocation($this->centerLatitude, $this->centerLongitude);
$this->projectionCenterPx = $centerPoint->x * $this->areaScale;
$this->projectionCenterPy = $centerPoint->y * $this->areaScale;
}
public function projectLocation(float $latitude, float $longitude): object
{
$px = $this->projectionWidth / 2 + $longitude * $this->pixelsPerLonDegree;
$siny = sin($this->deg2rad($latitude));
$py = $this->projectionHeight / 2 + 0.5 * log((1 + $siny) / (1 - $siny)) * -$this->pixelsPerLonRadian;
return (object) ['x' => $px, 'y' => $py];
}
private function deg2rad(float $deg): float
{
return ($deg * \M_PI) / 180;
}
public function getLocation(int $px, int $py): object
{
$x = $this->projectionCenterPx + ($px - $this->areaWidthPx / 2);
$y = $this->projectionCenterPy + ($py - $this->areaHeightPx / 2);
return $this->projectPx($x / $this->areaScale, $y / $this->areaScale);
}
public function getPoint(float $latitude, float $longitude): object
{
$point = $this->projectLocation($latitude, $longitude);
$x = ($point->x * $this->areaScale - $this->projectionCenterPx) + $this->areaWidthPx / (2 / $this->mapScale);
$y = ($point->y * $this->areaScale - $this->projectionCenterPy) + $this->areaHeightPx / (2 / $this->mapScale);
return new Pixel((int) round($x), (int) round($y));
}
public function projectPx(float $px, float $py): object
{
$longitude = ($px - $this->projectionWidth / 2) / $this->pixelsPerLonDegree;
$latitudeRadians = ($py - $this->projectionHeight / 2) / -$this->pixelsPerLonRadian;
$latitude = $this->rad2deg(2 * atan(exp($latitudeRadians)) - \M_PI / 2);
return new Point($latitude,$longitude);
}
private function rad2deg(float $rad): float
{
return ($rad * 180) / \M_PI;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment