c962d5e6e1
* various (294 files) pep8 doc fixes & pylint fixes svn: r10103
845 lines
37 KiB
Python
845 lines
37 KiB
Python
#
|
|
# Gramps - a GTK+/GNOME based genealogy program
|
|
#
|
|
# Copyright (C) 2001-2006 Donald N. Allingham
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program 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 General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
#
|
|
|
|
# $Id:MapView.py 9912 2008-01-22 09:17:46Z acraphae $
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Python modules
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
from gettext import gettext as _
|
|
import gc
|
|
import logging
|
|
import os
|
|
import math
|
|
import urllib
|
|
import urllib2
|
|
from xml.dom.minidom import parseString as xmlStringParser
|
|
|
|
log = logging.getLogger(".")
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# GTK/Gnome modules
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
import gobject
|
|
import gtk
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Gramps Modules
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
import PageView
|
|
import Config
|
|
import const
|
|
|
|
use_online_map = Config.get(Config.ONLINE_MAPS)
|
|
|
|
# Some initial places for debugging
|
|
glob_loc_data = [ # (Name, longitude, latitude)
|
|
("_Center", 0,0),
|
|
("_North",0,90),
|
|
("_South",0,-90),
|
|
("_West",-180,0),
|
|
("_East",180,0),
|
|
("Chicago",-87.75,41.83),
|
|
("Berlin",13.42,52.53),
|
|
("Honolulu",-157.83,21.32),
|
|
("Madrid",-3.72,40.42),
|
|
("Moscow",37.70,55.75),
|
|
("Vienna",16.37,48.22),
|
|
("Sydney",151.17,-33.92),
|
|
("Rio de Janeiro",-43.28,-22.88),
|
|
("Tokyo",139.75,35.67),
|
|
("Cape Town",18.47,-33.93),
|
|
("Anchorage",-150.00,61.17),
|
|
("Mannheim-Wallstadt",8.55,49.48),
|
|
("Mannheim-Neckarau",8.48,49.45),
|
|
("Gorxheimertal",8.73,49.53)]
|
|
|
|
enable_debug = False
|
|
|
|
|
|
# Draws a map image and tries to allocate space in the correct aspect ratio
|
|
class GuideMap(gtk.DrawingArea):
|
|
def __init__(self, map_pixbuf):
|
|
gtk.DrawingArea.__init__(self)
|
|
self.map_pixbuf = map_pixbuf
|
|
self.connect("expose-event", self.expose_cb)
|
|
self.connect("size-allocate", self.size_allocate_cb)
|
|
self.gc = None
|
|
self.current_area = None
|
|
self.current_spot = None
|
|
self.old_size = (-1,-1)
|
|
|
|
# Set hightlight region
|
|
def hightlight_area( self, area):
|
|
self.current_area = area
|
|
self.queue_draw()
|
|
|
|
# Set hightlight region
|
|
def hightlight_spot( self, spot):
|
|
self.current_spot = spot
|
|
self.queue_draw()
|
|
|
|
# Redraw the image
|
|
def expose_cb(self,widget,event):
|
|
if not self.gc:
|
|
self.gc = self.window.new_gc()
|
|
self.gc.set_foreground( self.get_colormap().alloc_color("red"))
|
|
self.gc.set_background( self.get_colormap().alloc_color("blue"))
|
|
if self.backbuf and self.gc:
|
|
self.window.draw_pixbuf( self.gc, self.backbuf, 0,0, 0,0, -1,-1)
|
|
if self.current_area:
|
|
r = self.map_to_screen(self.current_area[0],
|
|
self.current_area[1],
|
|
self.current_area[2],
|
|
self.current_area[3])
|
|
self.window.draw_rectangle( self.gc, False,
|
|
r[0],r[1],r[2],r[3])
|
|
if self.current_spot:
|
|
r = self.map_to_screen(self.current_spot[0],
|
|
self.current_spot[1])
|
|
self.window.draw_line( self.gc,0,r[1],
|
|
self.backbuf.get_width(),r[1])
|
|
self.window.draw_line( self.gc,r[0],0,
|
|
r[0],self.backbuf.get_height())
|
|
|
|
|
|
# Scale backbuffer
|
|
def size_allocate_cb(self,widget,allocation):
|
|
# Always request a height, that is half of the width
|
|
w = max( 128,allocation.width)
|
|
self.set_size_request(-1,w/2)
|
|
|
|
# only create new backbuffer if size is different
|
|
new_size = (allocation.width,allocation.height)
|
|
if new_size is not self.old_size:
|
|
self.old_size = new_size
|
|
self.backbuf = self.map_pixbuf.scale_simple(
|
|
self.old_size[0],
|
|
self.old_size[1],
|
|
gtk.gdk.INTERP_BILINEAR)
|
|
gc.collect()
|
|
|
|
def map_to_screen( self, x,y,w=None, h=None):
|
|
px = int((float(x) + 180.0) / 360.0 * self.backbuf.get_width())
|
|
py = int((90-float(y)) / 180.0 * self.backbuf.get_height())
|
|
if w and h:
|
|
pw = int(float(w) / 360.0 * self.backbuf.get_width())
|
|
ph = int(float(h) / 180.0 * self.backbuf.get_height())
|
|
return (px,py,pw,ph)
|
|
return (px,py)
|
|
|
|
# Map tile files used by the ZoomMap
|
|
class MapTile:
|
|
def __init__( self, filename, x, y, w, h, pw, ph):
|
|
self.filename = filename
|
|
self.full_path = os.path.join(const.IMAGE_DIR,filename)
|
|
self.map_x = float(x)
|
|
self.map_y = float(y)
|
|
self.map_width = float(w)
|
|
self.map_height = float(h)
|
|
self.map_pixel_width = float(pw)
|
|
self.map_pixel_height = float(ph)
|
|
self.source_pixbuf = None
|
|
self.source_scale = self.map_pixel_width / self.map_width
|
|
self.scale = None
|
|
self.scaled_pixbuf = None
|
|
self.scaled_map_x = None
|
|
self.scaled_map_y = None
|
|
|
|
def set_viewport( self, target_scale, x_in, y_in, w_in, h_in):
|
|
# intersect viewport with map area
|
|
x = max(self.map_x, max(-180.0,x_in))
|
|
y = min(self.map_y, min( 90.0,y_in))
|
|
xmax = min( min(180.0,x_in+w_in), self.map_x+self.map_width)
|
|
ymin = max( max(-90.0,y_in-h_in), self.map_y-self.map_width)
|
|
w = xmax-x
|
|
h = y-ymin
|
|
|
|
if w > 0.0 and h > 0.0:
|
|
# crop source tile to not scale the whole IMAGE_DIR
|
|
xoffset = max(0,math.floor((x - self.map_x) * self.source_scale))
|
|
xmax = max(0,math.ceil((x+w - self.map_x) * self.source_scale))
|
|
yoffset = min(self.map_pixel_width,math.floor(-(y - self.map_y) * self.source_scale))
|
|
ymax = min(self.map_pixel_height,math.ceil(-(y-h - self.map_y) * self.source_scale))
|
|
|
|
rescale = target_scale / self.source_scale
|
|
if int((xmax-xoffset)*rescale) > 0 and int((ymax-yoffset)*rescale) > 0:
|
|
self.scaled_map_x = self.map_x + xoffset / self.source_scale
|
|
self.scaled_map_y = self.map_y - yoffset / self.source_scale
|
|
self.scaled_map_pixel_w = int((xmax-xoffset)*rescale)
|
|
self.scaled_map_pixel_h = int((ymax-yoffset)*rescale)
|
|
|
|
if enable_debug:
|
|
print
|
|
print "Source-Map origin: %f x %f, %f, %f" % (self.map_x,self.map_y,self.map_width,self.map_height)
|
|
print "Source-Map pixels: %f x %f" % (self.map_pixel_width,self.map_pixel_height)
|
|
print "Source-Map scale: %f" % self.source_scale
|
|
print "Target origin: %f x %f, %f, %f" % (x,y,w, h)
|
|
print "Target scale: %f" % target_scale
|
|
print "Target crop: %f x %f, %f x %f" % (xoffset,yoffset,xmax,ymax)
|
|
print "Origin of crop: %f x %f" % (self.scaled_map_x,self.scaled_map_y)
|
|
print "scaled tile size: %f x %f pix" % (self.scaled_map_pixel_w,self.scaled_map_pixel_h)
|
|
|
|
try:
|
|
if not self.source_pixbuf:
|
|
self.source_pixbuf = gtk.gdk.pixbuf_new_from_file( self.full_path)
|
|
|
|
clip_pixbuf = self.source_pixbuf.subpixbuf(int(xoffset),int(yoffset),int(xmax-xoffset),int(ymax-yoffset))
|
|
|
|
self.scale = target_scale
|
|
self.scaled_pixbuf = clip_pixbuf.scale_simple( int((xmax-xoffset)*rescale), int((ymax-yoffset)*rescale), gtk.gdk.INTERP_BILINEAR)
|
|
clip_pixbuf = None
|
|
except:
|
|
pass
|
|
|
|
else:
|
|
self.scale = None
|
|
self.scaled_pixbuf = None
|
|
else:
|
|
self.scale = None
|
|
self.scaled_pixbuf = None
|
|
|
|
def free(self):
|
|
self.scale = None
|
|
self.scaled_pixbuf = None
|
|
|
|
class WMSMapTile:
|
|
def __init__(self,capabilities,change_cb=None):
|
|
self.change_cb = change_cb
|
|
self.scaled_pixbuf = None
|
|
self.handler_running = False
|
|
self.target_scale = 0.0
|
|
self.scaled_map_x = 0.0
|
|
self.scaled_map_y = 0.0
|
|
self.scaled_map_pixel_w = 0.0
|
|
self.scaled_map_pixel_h = 0.0
|
|
self.capabilities_url = capabilities
|
|
u_reader = urllib2.urlopen(self.capabilities_url)
|
|
# TODO: Put into an idle handler or thread
|
|
response_body = u_reader.read()
|
|
u_reader.close()
|
|
xml_doc = xmlStringParser( response_body)
|
|
# validate name of root element
|
|
e = xml_doc.documentElement
|
|
if e.nodeName != "WMT_MS_Capabilities":
|
|
print "unsupported Document type '%s'" % e.nodeName
|
|
return None
|
|
self.map_request_params = {}
|
|
self.map_request_params["VERSION"] = e.getAttribute("version")
|
|
self.map_request_params["REQUEST"] = "GetMap"
|
|
self.map_request_params["SERVICE"] = "WMS"
|
|
self.map_request_params["REQUEST"] = "GetMap"
|
|
self.map_request_params["REQUEST"] = "GetMap"
|
|
self.map_request_params["FORMAT"] = "image/png"
|
|
self.map_request_params["SRS"] = "epsg:4326"
|
|
self.map_request_params["LAYERS"] = ""
|
|
# Child-nodes of root element
|
|
for n in e.childNodes:
|
|
if n.nodeName == "Service":
|
|
# Parse Service header
|
|
map_title = n.getElementsByTagName("Title")[0].firstChild.data
|
|
print " MAP Title: %s" % map_title
|
|
try:
|
|
map_fees = e.getElementsByTagName("Fees")[0].firstChild.data
|
|
except IndexError:
|
|
map_fees = "-"
|
|
print " MAP Fees: %s" % map_fees
|
|
try:
|
|
map_access_constraints = e.getElementsByTagName("AccessConstraints")[0].firstChild.data
|
|
except IndexError:
|
|
map_access_constraints = "-"
|
|
print " MAP AccessConstraints: %s" % map_access_constraints
|
|
|
|
elif n.nodeName == "Capability":
|
|
# Parse Capabilities
|
|
for n2 in n.childNodes:
|
|
if n2.nodeName == "Request":
|
|
t1 = n2.getElementsByTagName("GetMap")[0]
|
|
t2 = t1.getElementsByTagName("DCPType")[0]
|
|
t3 = t2.getElementsByTagName("HTTP")[0]
|
|
t4 = t3.getElementsByTagName("Get")[0]
|
|
t5 = t4.getElementsByTagName("OnlineResource")[0]
|
|
self.map_get_url = t5.getAttribute("xlink:href")
|
|
if enable_debug:
|
|
print(" Map Tile base url: %s" % self.map_get_url)
|
|
elif n2.nodeName == "Layer":
|
|
# parse Layers
|
|
if not self._srs_is_supported(n2.getElementsByTagName("SRS")[0].firstChild.data):
|
|
print "Layer coordinates not supported :-("
|
|
return None
|
|
layer_title = n2.getElementsByTagName("Title")[0].firstChild.data
|
|
if enable_debug:
|
|
print " Layer: %s" % layer_title
|
|
for n3 in n2.childNodes:
|
|
if n3.nodeName == "Layer":
|
|
# parse Layers
|
|
layer_title = n3.getElementsByTagName("Name")[0].firstChild.data
|
|
if enable_debug:
|
|
print " - Layer: %s" % layer_title
|
|
if self.map_request_params["LAYERS"]:
|
|
self.map_request_params["LAYERS"] += ","
|
|
self.map_request_params["LAYERS"] += layer_title
|
|
def _srs_is_supported( self, srs_string):
|
|
list = srs_string.lower().split()
|
|
if "epsg:4326" in list:
|
|
return True
|
|
return False
|
|
|
|
def idle_handler( self):
|
|
""" fetches parts of the download and feeds them into a PixbufLoader """
|
|
if enable_debug:
|
|
print "idle_handler"
|
|
if not self.handler_running or not self.url_handler:
|
|
return False
|
|
buf = self.url_handler.read(5000)
|
|
if enable_debug:
|
|
print len(buf)
|
|
if len(buf) > 0:
|
|
self.pixbufloader.write(buf)
|
|
self.scaled_pixbuf = self.pixbufloader.get_pixbuf()
|
|
else:
|
|
if enable_debug:
|
|
print "no more content."
|
|
self.handler_running = False
|
|
self.url_handler.close()
|
|
try:
|
|
self.pixbufloader.close()
|
|
except gobject.GError:
|
|
pass # dont crash if nothing or not an image has been downloaded
|
|
return False
|
|
self.change_cb()
|
|
return True
|
|
|
|
def set_viewport( self, target_scale, x_in, y_in, w_in, h_in):
|
|
new_scaled_map_pixel_w = int(w_in*target_scale)
|
|
new_scaled_map_pixel_h = int(h_in*target_scale)
|
|
if self.target_scale != target_scale\
|
|
or self.scaled_map_x > x_in\
|
|
or self.scaled_map_y < y_in\
|
|
or self.scaled_map_pixel_w < new_scaled_map_pixel_w\
|
|
or self.scaled_map_pixel_h < new_scaled_map_pixel_h:
|
|
# only download new map is new viewport is larger
|
|
# TODO: Fix above to be correct and add a cache of already downloaded tiles
|
|
self.target_scale = target_scale
|
|
self.scaled_map_x = x_in
|
|
self.scaled_map_y = y_in
|
|
self.scaled_map_pixel_w = int(w_in*target_scale)
|
|
self.scaled_map_pixel_h = int(h_in*target_scale)
|
|
self.map_request_params["BBOX"] = "%f,%f,%f,%f" % (x_in,y_in-h_in,x_in+w_in,y_in) # Neds to be set for request
|
|
self.map_request_params["WIDTH"] = int(w_in*target_scale)
|
|
self.map_request_params["HEIGHT"] = int(h_in*target_scale)
|
|
params = urllib.urlencode(self.map_request_params)
|
|
if self.handler_running:
|
|
if enable_debug:
|
|
print "stopping current download"
|
|
self.url_handler.close()
|
|
try:
|
|
self.pixbufloader.close()
|
|
except gobject.GError:
|
|
pass # dont crash if nothing or not an image has been downloaded
|
|
self.scaled_pixbuf = None
|
|
self.url_handler = urllib.urlopen(self.map_get_url+params)
|
|
self.handler_running = gobject.idle_add(self.idle_handler)
|
|
self.pixbufloader = gtk.gdk.PixbufLoader()
|
|
else:
|
|
if enable_debug:
|
|
print "new viewport fits inside old one. No download required."
|
|
|
|
def free(self):
|
|
pass
|
|
|
|
# Zoomable map image
|
|
class ZoomMap( gtk.DrawingArea):
|
|
def __init__( self, place_marker_pixbuf, hightlight_marker_pixbuf):
|
|
gtk.DrawingArea.__init__(self)
|
|
self.place_marker_pixbuf = place_marker_pixbuf
|
|
self.hightlight_marker_pixbuf = hightlight_marker_pixbuf
|
|
self.add_events(gtk.gdk.POINTER_MOTION_MASK) # position overlay
|
|
self.connect("expose-event", self.expose_cb)
|
|
self.connect("size-allocate", self.size_allocate_cb)
|
|
if enable_debug:
|
|
self.connect("motion-notify-event", self.motion_notify_event_cb)
|
|
self.gc = None
|
|
self.current_pixel_size = (-1,-1)
|
|
self.zoom_pos = (0,0)
|
|
self.magnifer = 0.0 # in pixel per degree
|
|
self.guide = None
|
|
self.textlayout = self.create_pango_layout("")
|
|
self.map_sources = {}
|
|
self.initial_exposed = False
|
|
if use_online_map:
|
|
self.map_sources[0.0] = []
|
|
self.map_sources[0.0].append(WMSMapTile("http://www2.demis.nl/wms/wms.asp?wms=WorldMap&VERSION=1.1.1&REQUEST=GetCapabilities",self.queue_draw))
|
|
|
|
def add_map_source( self,filename, x, y, w, h,pw,ph):
|
|
tile = MapTile( filename, x, y, w, h, pw, ph)
|
|
if not tile.source_scale in self.map_sources:
|
|
self.map_sources[tile.source_scale] = []
|
|
self.map_sources[tile.source_scale].append( tile)
|
|
|
|
# Set the guide map that should follow the zoom area
|
|
def set_guide( self, guide):
|
|
self.guide = guide
|
|
|
|
def set_location_model( self, model, idx_name, idx_long, idx_lat):
|
|
self.location_model = model
|
|
self.idx_name = idx_name
|
|
self.idx_long = idx_long
|
|
self.idx_lat = idx_lat
|
|
|
|
def motion_notify_event_cb(self,widget,event):
|
|
self.textlayout.set_text( "Position: %03.0f,%03.0f pixel" % (event.x,event.y))
|
|
(w, h) = self.textlayout.get_pixel_size()
|
|
self.gc.set_foreground( self.get_colormap().alloc_color("white"))
|
|
self.window.draw_rectangle( self.gc, True, 10,50,w, h)
|
|
self.gc.set_foreground( self.get_colormap().alloc_color("red"))
|
|
self.window.draw_layout( self.gc, 10, 50, self.textlayout)
|
|
(lon,lat) = self.screen_to_map(event.x,event.y)
|
|
self.textlayout.set_text( "Position: %03.0f,%03.0f degree" % (lon,lat))
|
|
(w, h) = self.textlayout.get_pixel_size()
|
|
self.gc.set_foreground( self.get_colormap().alloc_color("white"))
|
|
self.window.draw_rectangle( self.gc, True, 10,70,w, h)
|
|
self.gc.set_foreground( self.get_colormap().alloc_color("red"))
|
|
self.window.draw_layout( self.gc, 10, 70, self.textlayout)
|
|
|
|
# Redraw the image
|
|
def expose_cb(self,widget,event):
|
|
if not self.gc:
|
|
self.gc = self.window.new_gc()
|
|
self.gc.set_foreground( self.get_colormap().alloc_color("red"))
|
|
self.gc.set_background( self.get_colormap().alloc_color("blue"))
|
|
if not self.backbuf:
|
|
self.size_allocate_cb( self,self.get_allocation())
|
|
if self.backbuf and self.gc:
|
|
#draw all maps
|
|
scales = self.map_sources.keys()
|
|
scales.sort()
|
|
for scale in scales:
|
|
for map in self.map_sources[scale]:
|
|
if map.scaled_pixbuf:
|
|
(px,py) = self.map_to_screen( map.scaled_map_x, map.scaled_map_y)
|
|
self.window.draw_pixbuf( self.gc, map.scaled_pixbuf, 0,0, px,py)
|
|
if enable_debug:
|
|
self.window.draw_rectangle( self.gc, False, px,py,map.scaled_map_pixel_w,map.scaled_map_pixel_h)
|
|
self.window.draw_line( self.gc, px,py,px+map.scaled_map_pixel_w,py+map.scaled_map_pixel_h)
|
|
self.window.draw_line( self.gc, px,py+map.scaled_map_pixel_h,px+map.scaled_map_pixel_w,py)
|
|
gc.collect()
|
|
|
|
|
|
# draw all available locations
|
|
if self.location_model:
|
|
iter = self.location_model.get_iter_first()
|
|
while iter:
|
|
(n,x,y) = self.location_model.get( iter, self.idx_name, self.idx_long, self.idx_lat)
|
|
(px,py) = self.map_to_screen( x, y)
|
|
#if px > 0 and py > 0 and px < self.backbuf.get_width() and py < self.backbuf.get_height():
|
|
# draw only visible markers
|
|
#self.window.draw_pixbuf(
|
|
# self.gc,
|
|
# self.place_marker_pixbuf,
|
|
# 0,0,
|
|
# px-self.place_marker_pixbuf.get_width()/2,
|
|
# py-self.place_marker_pixbuf.get_height()/2,
|
|
# -1,-1)
|
|
self.textlayout.set_text(n)
|
|
self.window.draw_layout(
|
|
self.gc,
|
|
px,py,
|
|
self.textlayout)
|
|
iter = self.location_model.iter_next( iter)
|
|
|
|
# hightlight current location
|
|
(px,py) = self.map_to_screen( self.zoom_pos[0], self.zoom_pos[1])
|
|
self.window.draw_pixbuf(
|
|
self.gc,
|
|
self.hightlight_marker_pixbuf,
|
|
0,0,
|
|
px-self.hightlight_marker_pixbuf.get_width()/2,
|
|
py-self.hightlight_marker_pixbuf.get_height()/2,
|
|
-1,-1)
|
|
#self.window.draw_rectangle( self.gc, False, px-3,py-3, 6,6)
|
|
|
|
#(px1,py1) = self.map_to_screen(-180, 90)
|
|
#(px2,py2) = self.map_to_screen( 180,-90)
|
|
#self.window.draw_rectangle( self.gc, False, px1,py1,px2-px1,py2-py1)
|
|
#self.window.draw_line( self.gc, px1,py1,px2,py2)
|
|
#self.window.draw_line( self.gc, px1,py2,px2,py1)
|
|
if enable_debug:
|
|
# Output debugging info
|
|
self.textlayout.set_text( "Magnifer: %f pixel per degree" % self.magnifer)
|
|
self.window.draw_layout( self.gc, 10, 10, self.textlayout)
|
|
self.textlayout.set_text( "Current map: %f pixel per degree" % self.selected_map_scale)
|
|
self.window.draw_layout( self.gc, 10, 30, self.textlayout)
|
|
|
|
def map_to_screen( self, lon, lat):
|
|
px = int(self.current_pixel_size[0] / 2.0 + (lon - self.zoom_pos[0]) * self.magnifer)
|
|
py = int(self.current_pixel_size[1] / 2.0 - (lat - self.zoom_pos[1]) * self.magnifer)
|
|
return( px, py)
|
|
|
|
def screen_to_map( self, px, py):
|
|
px = float(px)
|
|
py = float(py)
|
|
lon = (px - self.current_pixel_size[0]/2) / self.magnifer + self.zoom_pos[0];
|
|
lat = -(py - self.current_pixel_size[1]/2) / self.magnifer + self.zoom_pos[1];
|
|
return( lon, lat)
|
|
|
|
# Scale backbuffer
|
|
def size_allocate_cb(self,widget,allocation):
|
|
# only create new backbuffer if size is different
|
|
new_size = (allocation.width,allocation.height)
|
|
if new_size is not self.current_pixel_size or not self.backbuf:
|
|
self.backbuf = True
|
|
self.current_pixel_size = new_size
|
|
if self.magnifer == 0.0:
|
|
# scale map to full width
|
|
self.magnifer = self.current_pixel_size[0] / 360.0
|
|
x0,y0 = self.screen_to_map( 0, 0)
|
|
x1,y1 = self.screen_to_map( new_size[0], new_size[1])
|
|
self.guide.hightlight_area( (x0,y0,x1-x0, y0-y1))
|
|
|
|
def cmpfunc(s):
|
|
return((self.magnifer-s)*(self.magnifer-s))
|
|
|
|
# select best matching tile set
|
|
self.selected_map_scale = None
|
|
smallest_scale = None
|
|
largest_scale = None
|
|
for s in self.map_sources.keys():
|
|
if not self.selected_map_scale or cmpfunc(s) < cmpfunc(self.selected_map_scale):
|
|
self.selected_map_scale = s
|
|
if not smallest_scale or s < smallest_scale:
|
|
smallest_scale = s
|
|
if not largest_scale or s > largest_scale:
|
|
largest_scale = s
|
|
if enable_debug:
|
|
print "scale of display: %f" % self.magnifer
|
|
print "available map scales:"
|
|
print self.map_sources.keys()
|
|
print "largest scale: %f" % largest_scale
|
|
print "smallest scale: %f" % smallest_scale
|
|
print "selected scale: %f" % self.selected_map_scale
|
|
|
|
for s in self.map_sources.keys():
|
|
for map in self.map_sources[s]:
|
|
if s == self.selected_map_scale or s == smallest_scale:
|
|
map.set_viewport( self.magnifer, x0, y0, x1-x0, y0-y1)
|
|
else:
|
|
map.free()
|
|
|
|
# Scroll to requested position
|
|
def scroll_to( self, long, lat):
|
|
self.zoom_pos = (float( min(180,(max(-180,long)))), float(min(90,(max(-90,lat)))))
|
|
self.backbuf = None
|
|
if self.guide:
|
|
self.guide.hightlight_spot( self.zoom_pos)
|
|
self.queue_draw()
|
|
|
|
def zoom_out(self):
|
|
self.magnifer = max( 1.0, self.magnifer * 2.0/3.0)
|
|
self.backbuf = None
|
|
self.queue_draw()
|
|
|
|
def zoom_in(self):
|
|
self.magnifer = min( 1000.0, self.magnifer * 1.5)
|
|
self.backbuf = None
|
|
self.queue_draw()
|
|
|
|
def zoom_normal(self):
|
|
self.magnifer = 1.0
|
|
self.magnifer = self.selected_map_scale
|
|
self.backbuf = None
|
|
self.queue_draw()
|
|
|
|
def zoom_fit(self):
|
|
self.magnifer = self.current_pixel_size[0] / 360.0
|
|
self.backbuf = None
|
|
self.queue_draw()
|
|
|
|
|
|
# Place list widget
|
|
class MapPlacesList(gtk.TreeView):
|
|
def __init__(self, data):
|
|
self.lstore = gtk.ListStore(
|
|
gobject.TYPE_STRING,
|
|
gobject.TYPE_FLOAT,
|
|
gobject.TYPE_FLOAT)
|
|
|
|
self.change_data( data)
|
|
|
|
gtk.TreeView.__init__(self, self.lstore)
|
|
self.set_rules_hint(True)
|
|
self.set_search_column(0)
|
|
|
|
column = gtk.TreeViewColumn('Place', gtk.CellRendererText(), text=0)
|
|
column.set_sort_column_id(0)
|
|
self.append_column(column)
|
|
|
|
column = gtk.TreeViewColumn('Lat', gtk.CellRendererText(), text=1)
|
|
column.set_sort_column_id(1)
|
|
self.append_column(column)
|
|
|
|
column = gtk.TreeViewColumn('Long', gtk.CellRendererText(),text=2)
|
|
column.set_sort_column_id(2)
|
|
self.append_column(column)
|
|
|
|
def change_data( self, data):
|
|
self.lstore.clear()
|
|
for item in data:
|
|
iter = self.lstore.append()
|
|
self.lstore.set(iter,
|
|
0, item[0],
|
|
1, item[1],
|
|
2, item[2])
|
|
|
|
|
|
|
|
# Map View main class
|
|
class MapView(PageView.PageView):
|
|
def __init__(self,dbstate,uistate):
|
|
PageView.PageView.__init__(self, _('Maps'), dbstate, uistate)
|
|
dbstate.connect('database-changed',self.change_db)
|
|
self.current_marker = None
|
|
|
|
def navigation_type(self):
|
|
return PageView.NAVIGATION_NONE
|
|
|
|
def define_actions(self):
|
|
self._add_action('ZoomIn',gtk.STOCK_ZOOM_IN,
|
|
_("Zoom _In"),tip=_("Zoom in by a factor of 2"),
|
|
callback=self.zoom_in_cb)
|
|
self._add_action('ZoomOut',gtk.STOCK_ZOOM_OUT,
|
|
_("Zoom _Out"),tip=_("Zoom out by a factor of 2"),
|
|
callback=self.zoom_out_cb)
|
|
self._add_action('ZoomNormal',gtk.STOCK_ZOOM_100,
|
|
_("_Normal Size"), tip=_("Return to normal size"),
|
|
callback=self.zoom_100_cb)
|
|
self._add_action('ZoomFit',gtk.STOCK_ZOOM_FIT,
|
|
_("Best _Fit"),
|
|
tip=_("Produce the best fit of the map in the window"),
|
|
callback=self.zoom_fit_cb)
|
|
|
|
def get_stock(self):
|
|
"""
|
|
Return the name of the stock icon to use for the display.
|
|
This assumes that this icon has already been registered with
|
|
GNOME as a stock icon.
|
|
"""
|
|
return 'gramps-map'
|
|
|
|
|
|
# For debugging: Reads in location from xearth
|
|
def get_xearth_markers(self):
|
|
data = []
|
|
try:
|
|
f = open("/etc/xearth/xearth.markers")
|
|
l = f.readline()
|
|
#linere = re.compile('[^0-9.-]*(-?[0-9]+\.[0-9]+)[^0-9.-]*(-?[0-9]+\.[0-9]+).*"([^"])".*', "I")
|
|
while l:
|
|
if not l[0] == "#":
|
|
l = l.strip().replace('"',"").replace(" "," ").replace(" "," ").replace(" "," ").replace(" # ",", ")
|
|
m = l.split( None, 2)
|
|
if len(m) == 3:
|
|
data.append( (m[2],float(m[1]),float(m[0])))
|
|
l = f.readline()
|
|
except IOError:
|
|
pass
|
|
return data
|
|
|
|
# Reads in locations from current GRAMPS database
|
|
def get_markers_from_database(self, db):
|
|
data = []
|
|
for place_handle in db.get_place_handles():
|
|
place = db.get_place_from_handle( place_handle)
|
|
if place:
|
|
try:
|
|
data.append( (place.get_title(),float(place.get_longitude()),float(place.get_latitude())))
|
|
except (TypeError, ValueError):
|
|
# ignore places that dont have usable data
|
|
pass
|
|
return data
|
|
|
|
# Reads in textfiles from NIMA:
|
|
# http://earth-info.nga.mil/gns/html/cntry_files.html
|
|
def parse_nima_countryfile(self, filename):
|
|
import csv
|
|
data = []
|
|
try:
|
|
csvreader = csv.reader(open(filename), "excel-tab")
|
|
l = csvreader.next() # skip header
|
|
l = csvreader.next()
|
|
line = 1
|
|
while l:
|
|
if l[17] == "N" and l[9] == "P":
|
|
city = l[22]
|
|
lat = float( l[3])
|
|
lon = float( l[4])
|
|
|
|
if line % 10 == 0:
|
|
data.append( (city, lon, lat))
|
|
l = csvreader.next()
|
|
line = line + 1
|
|
except (IOError,StopIteration):
|
|
pass
|
|
return data
|
|
|
|
def build_widget(self):
|
|
hbox = gtk.HBox( False, 4)
|
|
hbox.set_border_width( 4)
|
|
|
|
no = gtk.Image()
|
|
# The large zoomable map
|
|
self.zoom_map = ZoomMap(
|
|
gtk.gdk.pixbuf_new_from_file(os.path.join(const.IMAGE_DIR,"bad.png")),
|
|
gtk.gdk.pixbuf_new_from_file(os.path.join(const.IMAGE_DIR,"good.png")))
|
|
if not use_online_map:
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_0_0.jpg', -180,90, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_0_1.jpg', -135,90, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_0_2.jpg', -90,90, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_0_3.jpg', -45,90, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_0_4.jpg', 0,90, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_0_5.jpg', 45,90, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_0_6.jpg', 90,90, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_0_7.jpg', 135,90, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_1_0.jpg', -180,45, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_1_1.jpg', -135,45, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_1_2.jpg', -90,45, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_1_3.jpg', -45,45, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_1_4.jpg', 0,45, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_1_5.jpg', 45,45, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_1_6.jpg', 90,45, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_1_7.jpg', 135,45, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_2_0.jpg', -180,0, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_2_1.jpg', -135,0, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_2_2.jpg', -90,0, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_2_3.jpg', -45,0, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_2_4.jpg', 0,0, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_2_5.jpg', 45,0, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_2_6.jpg', 90,0, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_2_7.jpg', 135,0, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_3_0.jpg', -180,-45, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_3_1.jpg', -135,-45, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_3_2.jpg', -90,-45, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_3_3.jpg', -45,-45, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_3_4.jpg', 0,-45, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_3_5.jpg', 45,-45, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_3_6.jpg', 90,-45, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x3200x1600_tile_3_7.jpg', 135,-45, 45,45, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x1600x800_tile_0_0.jpg', -180,90, 90,90, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x1600x800_tile_0_1.jpg', -90,90, 90,90, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x1600x800_tile_0_2.jpg', 0,90, 90,90, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x1600x800_tile_0_3.jpg', 90,90, 90,90, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x1600x800_tile_1_0.jpg', -180,0, 90,90, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x1600x800_tile_1_1.jpg', -90,0, 90,90, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x1600x800_tile_1_2.jpg', 0,0, 90,90, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x1600x800_tile_1_3.jpg', 90,0, 90,90, 400,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x800x400.jpg', -180,90, 360,180, 800,400)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x400x200.jpg', -180,90, 360,180, 400,200)
|
|
self.zoom_map.add_map_source('world.topo.200407.3x128x60.jpg', -180,90, 360,180, 128,60)
|
|
|
|
self.zoom_map.set_size_request(300,300)
|
|
hbox.pack_start( self.zoom_map, True, True, 0)
|
|
|
|
# On the right side
|
|
vbox = gtk.VBox( False, 4)
|
|
hbox.pack_start( vbox, False, False, 0)
|
|
|
|
# The small guide map
|
|
self.guide_map = GuideMap(
|
|
gtk.gdk.pixbuf_new_from_file(os.path.join(const.IMAGE_DIR,"world.topo.200407.3x128x60.jpg")))
|
|
self.guide_map.set_size_request(128,64)
|
|
vbox.pack_start( self.guide_map, False, True, 0)
|
|
|
|
self.zoom_map.set_guide(self.guide_map)
|
|
|
|
# and the place list
|
|
self.place_list_view = MapPlacesList( [])
|
|
self.zoom_map.set_location_model(self.place_list_view.get_model(), 0,1,2)
|
|
self.place_list_view.connect("cursor-changed", self.entry_select_cb)
|
|
self.place_list_view.set_size_request(128,-1)
|
|
vport = gtk.ScrolledWindow()
|
|
vbox.pack_start(vport,True,True,0)
|
|
vport.add( self.place_list_view)
|
|
|
|
self.rebuild_places()
|
|
|
|
return hbox
|
|
|
|
def ui_definition(self):
|
|
"""
|
|
Specifies the UIManager XML code that defines the menus and buttons
|
|
associated with the interface.
|
|
"""
|
|
return '''<ui>
|
|
<toolbar name="ToolBar">
|
|
<toolitem action="ZoomIn"/>
|
|
<toolitem action="ZoomOut"/>
|
|
<toolitem action="ZoomNormal"/>
|
|
<toolitem action="ZoomFit"/>
|
|
</toolbar>
|
|
</ui>'''
|
|
|
|
def change_db(self,db):
|
|
"""
|
|
Callback associated with DbState. Whenenver the database
|
|
changes, this task is called. In this case, we rebuild the
|
|
columns, and connect signals to the connected database. Tere
|
|
is no need to store the database, since we will get the value
|
|
from self.state.db
|
|
"""
|
|
db.connect('place-rebuild',self.rebuild_places)
|
|
db.connect('place-update',self.rebuild_places)
|
|
|
|
def rebuild_places(self, handle_list=None):
|
|
d = glob_loc_data
|
|
try:
|
|
d = d + self.get_xearth_markers()
|
|
#d = self.parse_nima_countryfile("/tmp/gm.txt")
|
|
d = d + self.get_markers_from_database( self.dbstate.db)
|
|
except:
|
|
log.error("Failed to rebuild places.", exc_info=True)
|
|
self.place_list_view.change_data( d)
|
|
|
|
def entry_select_cb(self,treeview):
|
|
s = treeview.get_selection()
|
|
(model,sel) = s.get_selected_rows()
|
|
for path in sel:
|
|
iter = model.get_iter(path)
|
|
self.zoom_map.scroll_to(model.get_value(iter,1),
|
|
model.get_value(iter,2))
|
|
break
|
|
|
|
def zoom_in_cb(self, obj):
|
|
self.zoom_map.zoom_in()
|
|
|
|
def zoom_out_cb(self, obj):
|
|
self.zoom_map.zoom_out()
|
|
|
|
def zoom_100_cb(self, obj):
|
|
self.zoom_map.zoom_normal()
|
|
|
|
def zoom_fit_cb(self, obj):
|
|
self.zoom_map.zoom_fit()
|