mirror of
https://github.com/OSURoboticsClub/Rover_2017_2018.git
synced 2025-11-09 02:31:14 +00:00
Moved file to software/ground_station
This commit is contained in:
333
software/ground_station/Framework/MapSystems/RoverMap.py
Normal file
333
software/ground_station/Framework/MapSystems/RoverMap.py
Normal file
@@ -0,0 +1,333 @@
|
||||
'''
|
||||
Mapping.py: Objected Orientated Google Maps for Python
|
||||
ReWritten by Chris Pham
|
||||
|
||||
Copyright OSURC, orginal code from GooMPy by Alec Singer and Simon D. Levy
|
||||
|
||||
This code is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
This code is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
||||
'''
|
||||
|
||||
#####################################
|
||||
# Imports
|
||||
#####################################
|
||||
# Python native imports
|
||||
import math
|
||||
import urllib2
|
||||
from io import StringIO, BytesIO
|
||||
import os
|
||||
import time
|
||||
import PIL.ImageDraw
|
||||
import signing
|
||||
import MapHelper
|
||||
|
||||
#####################################
|
||||
# Constants
|
||||
#####################################
|
||||
_KEYS = []
|
||||
# Number of pixels in half the earth's circumference at zoom = 21
|
||||
_EARTHPIX = 268435456
|
||||
# Number of decimal places for rounding coordinates
|
||||
_DEGREE_PRECISION = 4
|
||||
# Larget tile we can grab without paying
|
||||
_TILESIZE = 640
|
||||
# Fastest rate at which we can download tiles without paying
|
||||
_GRABRATE = 4
|
||||
# Pixel Radius of Earth for calculations
|
||||
_PIXRAD = _EARTHPIX / math.pi
|
||||
_DISPLAYPIX = _EARTHPIX / 2000
|
||||
|
||||
file_pointer = open('key', 'r')
|
||||
for i in file_pointer:
|
||||
_KEYS.append(i.rstrip())
|
||||
file_pointer.close()
|
||||
|
||||
|
||||
class GMapsStitcher(object):
|
||||
def __init__(self, width, height,
|
||||
latitude, longitude, zoom,
|
||||
maptype, radius_meters=None, num_tiles=4, debug=False):
|
||||
self.helper = MapHelper.MapHelper()
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
self.start_latitude = latitude
|
||||
self.start_longitude = longitude
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.zoom = zoom
|
||||
self.maptype = maptype
|
||||
self.radius_meters = radius_meters
|
||||
self.num_tiles = num_tiles
|
||||
self.display_image = self.helper.new_image(width, height)
|
||||
self.debug = debug
|
||||
|
||||
# Get the big image here
|
||||
self._fetch()
|
||||
self.center_display(latitude, longitude)
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
This string returns when used in a print statement
|
||||
Useful for debugging and to print current state
|
||||
|
||||
returns STRING
|
||||
"""
|
||||
string_builder = ""
|
||||
string_builder += ("Center of the displayed map: %4f, %4f\n" %
|
||||
(self.center_x, self.center_y))
|
||||
string_builder += ("Center of the big map: %4fx%4f\n" %
|
||||
(self.start_longitude, self.start_longitude))
|
||||
string_builder += ("Current latitude is: %4f, %4f\n" %
|
||||
(self.longitude, self.latitude))
|
||||
string_builder += ("The top-left of the box: %dx%d\n" %
|
||||
(self.left_x, self.upper_y))
|
||||
string_builder += ("Number of tiles genreated: %dx%d\n" %
|
||||
(self.num_tiles, self.num_tiles))
|
||||
string_builder += "Map Type: %s\n" % (self.maptype)
|
||||
string_builder += "Zoom Level: %s\n" % (self.zoom)
|
||||
string_builder += ("Dimensions of Big Image: %dx%d\n" %
|
||||
(self.big_image.size[0], self.big_image.size[1]))
|
||||
string_builder += ("Dimensions of Displayed Image: %dx%d\n" %
|
||||
(self.width, self.height))
|
||||
string_builder += ("LatLong of Northwest Corner: %4f, %4f\n" %
|
||||
(self.northwest))
|
||||
string_builder += ("LatLong of Southeast Corner: %4f, %4f\n" %
|
||||
(self.southeast))
|
||||
return string_builder
|
||||
|
||||
def _grab_tile(self, longitude, latitude, sleeptime=0):
|
||||
"""
|
||||
This will return the tile at location longitude x latitude.
|
||||
Includes a sleep time to allow for free use if there is no API key
|
||||
|
||||
returns PIL.IMAGE OBJECT
|
||||
"""
|
||||
# Make the url string for polling
|
||||
# GET request header gets appended to the string
|
||||
urlbase = 'https://maps.googleapis.com/maps/api/staticmap?'
|
||||
urlbase += 'center=%.4f,%.4f&zoom=%d&maptype=%s'
|
||||
urlbase += '&size=%dx%d&format=png&key=%s'
|
||||
|
||||
# Fill the formatting
|
||||
specs = (self.helper.fast_round(latitude, _DEGREE_PRECISION),
|
||||
self.helper.fast_round(longitude, _DEGREE_PRECISION),
|
||||
self.zoom, self.maptype, _TILESIZE, _TILESIZE, _KEYS[0])
|
||||
filename = 'Resources/Maps/' + ('%.4f_%.4f_%d_%s_%d_%d_%s' % specs)
|
||||
filename += '.png'
|
||||
|
||||
# Tile Image object
|
||||
tile_object = None
|
||||
|
||||
if os.path.isfile(filename):
|
||||
tile_object = PIL.Image.open(filename)
|
||||
|
||||
# If file on filesystem
|
||||
else:
|
||||
# make the url
|
||||
url = urlbase % specs
|
||||
url = signing.sign_url(url, _KEYS[1])
|
||||
result = urllib2.urlopen(urllib2.Request(url)).read()
|
||||
tile_object = PIL.Image.open(BytesIO(result))
|
||||
if not os.path.exists('Resources/Maps'):
|
||||
os.mkdir('Resources/Maps')
|
||||
tile_object.save(filename)
|
||||
# Added to prevent timeouts on Google Servers
|
||||
time.sleep(sleeptime)
|
||||
|
||||
return tile_object
|
||||
|
||||
def _pixels_to_lon(self, iterator, lon_pixels):
|
||||
"""
|
||||
This converts pixels to degrees to be used in
|
||||
fetching squares and generate correct squares
|
||||
|
||||
returns FLOAT(degrees)
|
||||
"""
|
||||
# Magic Lines, no idea
|
||||
degrees = self.helper.pixels_to_degrees((iterator - self.num_tiles / 2)
|
||||
* _TILESIZE, self.zoom)
|
||||
return math.degrees((lon_pixels + degrees - _EARTHPIX) / _PIXRAD)
|
||||
|
||||
def _pixels_to_lat(self, iterator, lat_pixels):
|
||||
"""
|
||||
This converts pixels to latitude using meridian projection
|
||||
to get the latitude to generate squares
|
||||
|
||||
returns FLOAT(degrees)
|
||||
"""
|
||||
# Magic Lines
|
||||
return math.degrees(math.pi / 2 - 2 * math.atan(math.exp(((lat_pixels +
|
||||
self.helper.pixels_to_degrees(
|
||||
(iterator - self.num_tiles / 2)
|
||||
* _TILESIZE, self.zoom))
|
||||
- _EARTHPIX) / _PIXRAD)))
|
||||
|
||||
def fetch_tiles(self):
|
||||
"""
|
||||
Function that handles fetching of files from init'd variables
|
||||
|
||||
returns PIL.IMAGE OBJECT, (WEST, NORTH), (EAST, SOUTH)
|
||||
|
||||
North/East/South/West are in FLOAT(degrees)
|
||||
"""
|
||||
# cap floats to precision amount
|
||||
self.latitude = self.helper.fast_round(self.latitude,
|
||||
_DEGREE_PRECISION)
|
||||
self.longitude = self.helper.fast_round(self.longitude,
|
||||
_DEGREE_PRECISION)
|
||||
|
||||
# number of tiles required to go from center
|
||||
# latitude to desired radius in meters
|
||||
if self.radius_meters is not None:
|
||||
self.num_tiles = (int(
|
||||
round(2 * self.helper.pixels_to_meters(
|
||||
self.latitude, self.zoom) /
|
||||
(_TILESIZE / 2. / self.radius_meters))))
|
||||
|
||||
lon_pixels = _EARTHPIX + self.longitude * math.radians(_PIXRAD)
|
||||
|
||||
sin_lat = math.sin(math.radians(self.latitude))
|
||||
lat_pixels = _EARTHPIX - _PIXRAD * math.log((1+sin_lat)/(1-sin_lat))/2
|
||||
self.big_size = self.num_tiles * _TILESIZE
|
||||
big_image = self.helper.new_image(self.big_size, self.big_size)
|
||||
|
||||
for j in range(self.num_tiles):
|
||||
lon = self._pixels_to_lon(j, lon_pixels)
|
||||
for k in range(self.num_tiles):
|
||||
lat = self._pixels_to_lat(k, lat_pixels)
|
||||
tile = self._grab_tile(lon, lat)
|
||||
big_image.paste(tile, (j * _TILESIZE, k * _TILESIZE))
|
||||
|
||||
west = self._pixels_to_lon(0, lon_pixels)
|
||||
east = self._pixels_to_lon(self.num_tiles - 1, lon_pixels)
|
||||
|
||||
north = self._pixels_to_lat(0, lat_pixels)
|
||||
south = self._pixels_to_lat(self.num_tiles - 1, lat_pixels)
|
||||
return big_image, (north, west), (south, east)
|
||||
|
||||
def move_pix(self, dx, dy):
|
||||
"""
|
||||
Function gets change in x and y (dx, dy)
|
||||
then displaces the displayed map that amount
|
||||
|
||||
NO RETURN
|
||||
"""
|
||||
self._constrain_x(dx)
|
||||
self._constrain_y(dy)
|
||||
self.update()
|
||||
|
||||
def _constrain_x(self, diff):
|
||||
"""
|
||||
Helper for move_pix
|
||||
"""
|
||||
new_value = self.left_x - diff
|
||||
|
||||
if !(new_value > 0 and
|
||||
(new_value < self.big_image.size[0] - self.width)):
|
||||
return self.left_x
|
||||
else:
|
||||
return new_value
|
||||
|
||||
def _constrain_y(self, diff):
|
||||
"""
|
||||
Helper for move_pix
|
||||
"""
|
||||
new_value = self.upper_y - diff
|
||||
|
||||
if !(new_value > 0 and
|
||||
new_value < self.big_image.size[1] - self.height):
|
||||
return self.upper_y
|
||||
else:
|
||||
return new_value
|
||||
|
||||
def update(self):
|
||||
"""
|
||||
Function remakes display image using top left corners
|
||||
"""
|
||||
self.display_image.paste(self.big_image, (-self.left_x, -self.upper_y))
|
||||
# self.display_image.resize((self.image_zoom, self.image_zoom))
|
||||
|
||||
def _fetch(self):
|
||||
"""
|
||||
Function generates big image
|
||||
"""
|
||||
self.big_image, self.northwest, self.southeast = self.fetch_tiles()
|
||||
|
||||
def move_latlon(self, lat, lon):
|
||||
"""
|
||||
Function to move the object/rover
|
||||
"""
|
||||
x, y = self._get_cartesian(lat, lon)
|
||||
self._constrain_x(self.center_x-x)
|
||||
self._constrain_y(self.center_y-y)
|
||||
self.update()
|
||||
|
||||
def _get_cartesian(self, lat, lon):
|
||||
"""
|
||||
Helper for getting the x, y given lat and lon
|
||||
|
||||
returns INT, INT (x, y)
|
||||
"""
|
||||
viewport_lat_nw, viewport_lon_nw = self.northwest
|
||||
viewport_lat_se, viewport_lon_se = self.southeast
|
||||
# print "Lat:", viewport_lat_nw, viewport_lat_se
|
||||
# print "Lon:", viewport_lon_nw, viewport_lon_se
|
||||
|
||||
viewport_lat_diff = viewport_lat_nw - viewport_lat_se
|
||||
viewport_lon_diff = viewport_lon_se - viewport_lon_nw
|
||||
|
||||
# print viewport_lon_diff, viewport_lat_diff
|
||||
|
||||
bigimage_width = self.big_image.size[0]
|
||||
bigimage_height = self.big_image.size[1]
|
||||
|
||||
pixel_per_lat = bigimage_height / viewport_lat_diff
|
||||
pixel_per_lon = bigimage_width / viewport_lon_diff
|
||||
# print "Pixel per:", pixel_per_lat, pixel_per_lon
|
||||
|
||||
new_lat_gps_range_percentage = (viewport_lat_nw - lat)
|
||||
new_lon_gps_range_percentage = (lon - viewport_lon_nw)
|
||||
# print lon, viewport_lon_se
|
||||
|
||||
x = new_lon_gps_range_percentage * pixel_per_lon
|
||||
y = new_lat_gps_range_percentage * pixel_per_lat
|
||||
|
||||
return int(x), int(y)
|
||||
|
||||
def add_gps_location(self, lat, lon, shape, size, fill):
|
||||
"""
|
||||
Function adds a shape at lat x lon
|
||||
"""
|
||||
x, y = self._get_cartesian(lat, lon)
|
||||
draw = PIL.ImageDraw.Draw(self.big_image)
|
||||
if shape is "ellipsis":
|
||||
draw.ellipsis((x-size, y-size, x+size, y+size), fill)
|
||||
else:
|
||||
draw.rectangle([x-size, y-size, x+size, y+size], fill)
|
||||
self.update()
|
||||
|
||||
def center_display(self, lat, lon):
|
||||
"""
|
||||
Function centers the display image
|
||||
"""
|
||||
x, y = self._get_cartesian(lat, lon)
|
||||
self.center_x = x
|
||||
self.center_y = y
|
||||
|
||||
self.left_x = (self.center_x - (self.width/2))
|
||||
self.upper_y = (self.center_y - (self.height/2))
|
||||
self.update()
|
||||
|
||||
# def update_rover_map_location(self, lat, lon):
|
||||
# print "I did nothing"
|
||||
|
||||
# def draw_circle(self, lat, lon, radius, fill):
|
||||
# print "I did nothing"
|
||||
@@ -0,0 +1,60 @@
|
||||
#####################################
|
||||
# Imports
|
||||
#####################################
|
||||
# Python native imports
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
import logging
|
||||
|
||||
import rospy
|
||||
|
||||
# Custom Imports
|
||||
import RoverMap
|
||||
|
||||
#####################################
|
||||
# Global Variables
|
||||
#####################################
|
||||
# put some stuff here later so you can remember
|
||||
|
||||
|
||||
class RoverMapCoordinator(QtCore.QThread):
|
||||
pixmap_ready_signal = QtCore.pyqtSignal(str)
|
||||
|
||||
def __init__(self, shared_objects):
|
||||
super(RoverMapCoordinator, self).init()
|
||||
|
||||
self.shared_objects = shared_objects
|
||||
self.left_screen = self.shared_objects["screens"]["left_screen"]
|
||||
self.mapping_label = self.left_screen.mapping_label
|
||||
|
||||
self.setings = QtCore.QSettings()
|
||||
|
||||
self.logger = logging.getLogger("groundstation")
|
||||
|
||||
self.run_thread_flag = True
|
||||
self.setup_map_flag = True
|
||||
|
||||
# setup map
|
||||
self._setup_map_threads()
|
||||
|
||||
def run(self):
|
||||
self.logger.debug("Starting Map Coordinator Thread")
|
||||
|
||||
while self.run_thread_flag:
|
||||
self.msleep(10)
|
||||
|
||||
self.__wait_for_map_thread()
|
||||
self.logger.debug("Stopping Map Coordinator Thread")
|
||||
|
||||
def __wait_for_map_thread(self):
|
||||
self.map_thread.wait()
|
||||
|
||||
def _setup_map_threads(self):
|
||||
self.map_thread = RoverMap.GMapsStitcher(1280,
|
||||
720, 44.567161, -123.278432,
|
||||
18, 'terrain', None, 20)
|
||||
|
||||
def pixmap_ready_slot(self):
|
||||
self.mapping_label.setPixmap(self.map_thread.display_image)
|
||||
|
||||
def on_kill_threads_requested_slot(self):
|
||||
self.run_thread_flag = False
|
||||
@@ -0,0 +1,43 @@
|
||||
import PIL.Image
|
||||
import math
|
||||
|
||||
|
||||
class MapHelper(object):
|
||||
|
||||
@staticmethod
|
||||
def new_image(width, height):
|
||||
"""
|
||||
Generates a new image using PIL.Image module
|
||||
|
||||
returns PIL.IMAGE OBJECT
|
||||
"""
|
||||
return PIL.Image.new('RGBA', (width, height))
|
||||
|
||||
@staticmethod
|
||||
def fast_round(value, precision):
|
||||
"""
|
||||
Function to round values instead of using python's
|
||||
|
||||
return INT
|
||||
"""
|
||||
return int(value * 10 ** precision) / 10. ** precision
|
||||
|
||||
@staticmethod
|
||||
def pixels_to_degrees(pixels, zoom):
|
||||
"""
|
||||
Generates pixels to be expected at zoom levels
|
||||
|
||||
returns INT
|
||||
"""
|
||||
return pixels * 2 ** (21-zoom)
|
||||
|
||||
@staticmethod
|
||||
def pixels_to_meters(latitude, zoom):
|
||||
"""
|
||||
Function generates how many pixels per meter it
|
||||
should be from the projecction
|
||||
|
||||
returns FLOAT
|
||||
"""
|
||||
# https://groups.google.com/forum/#!topic/google-maps-js-api-v3/hDRO4oHVSeM
|
||||
return 2 ** zoom / (156543.03392 * math.cos(math.radians(latitude)))
|
||||
53
software/ground_station/Framework/MapSystems/signing.py
Normal file
53
software/ground_station/Framework/MapSystems/signing.py
Normal file
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Signs a URL using a URL signing secret """
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import base64
|
||||
import urlparse
|
||||
|
||||
def sign_url(input_url=None, secret=None):
|
||||
""" Sign a request URL with a URL signing secret.
|
||||
|
||||
Usage:
|
||||
from urlsigner import sign_url
|
||||
|
||||
signed_url = sign_url(input_url=my_url, secret=SECRET)
|
||||
|
||||
Args:
|
||||
input_url - The URL to sign
|
||||
secret - Your URL signing secret
|
||||
|
||||
Returns:
|
||||
The signed request URL
|
||||
"""
|
||||
|
||||
if not input_url or not secret:
|
||||
raise Exception("Both input_url and secret are required")
|
||||
|
||||
url = urlparse.urlparse(input_url)
|
||||
|
||||
# We only need to sign the path+query part of the string
|
||||
url_to_sign = url.path + "?" + url.query
|
||||
|
||||
# Decode the private key into its binary format
|
||||
# We need to decode the URL-encoded private key
|
||||
decoded_key = base64.urlsafe_b64decode(secret)
|
||||
|
||||
# Create a signature using the private key and the URL-encoded
|
||||
# string using HMAC SHA1. This signature will be binary.
|
||||
signature = hmac.new(decoded_key, url_to_sign, hashlib.sha1)
|
||||
|
||||
# Encode the binary signature into base64 for use within a URL
|
||||
encoded_signature = base64.urlsafe_b64encode(signature.digest())
|
||||
|
||||
original_url = url.scheme + "://" + url.netloc + url.path + "?" + url.query
|
||||
|
||||
# Return signed URL
|
||||
return original_url + "&signature=" + encoded_signature
|
||||
|
||||
if __name__ == "__main__":
|
||||
input_url = raw_input("URL to Sign: ")
|
||||
secret = raw_input("URL signing secret: ")
|
||||
print "Signed URL: " + sign_url(input_url, secret)
|
||||
Reference in New Issue
Block a user