1010
1111from .utilities import (color_brewer , _parse_size , legend_scaler ,
1212 _locations_mirror , _locations_tolist , image_to_url ,
13- text_type , binary_type )
13+ text_type , binary_type ,
14+ none_min , none_max , iter_points ,
15+ )
1416
1517from .element import Element , Figure , JavascriptLink , CssLink , MacroElement
1618from .map import Layer , Icon , Marker , Popup
@@ -126,7 +128,6 @@ def render(self, **kwargs):
126128 JavascriptLink ("https://cdnjs.cloudflare.com/ajax/libs/leaflet-dvf/0.2/leaflet-dvf.markers.min.js" ), # noqa
127129 name = 'dvf_js' )
128130
129-
130131class Vega (Element ):
131132 def __init__ (self , data , width = None , height = None ,
132133 left = "0%" , top = "0%" , position = 'relative' ):
@@ -277,6 +278,35 @@ def style_data(self):
277278 self .style_function (feature ))
278279 return json .dumps (self .data )
279280
281+ def _get_self_bounds (self ):
282+ """Computes the bounds of the object itself (not including it's children)
283+ in the form [[lat_min, lon_min], [lat_max, lon_max]]
284+ """
285+ if not self .embed :
286+ raise ValueError ('Cannot compute bounds of non-embedded GeoJSON.' )
287+
288+ if 'features' not in self .data .keys ():
289+ # Catch case when GeoJSON is just a single Feature or a geometry.
290+ if not (isinstance (self .data , dict ) and 'geometry' in self .data .keys ()):
291+ # Catch case when GeoJSON is just a geometry.
292+ self .data = {'type' : 'Feature' , 'geometry' : self .data }
293+ self .data = {'type' : 'FeatureCollection' , 'features' : [self .data ]}
294+
295+ bounds = [[None ,None ],[None ,None ]]
296+ for feature in self .data ['features' ]:
297+ for point in iter_points (feature .get ('geometry' ,{}).get ('coordinates' ,{})):
298+ bounds = [
299+ [
300+ none_min (bounds [0 ][0 ], point [1 ]),
301+ none_min (bounds [0 ][1 ], point [0 ]),
302+ ],
303+ [
304+ none_max (bounds [1 ][0 ], point [1 ]),
305+ none_max (bounds [1 ][1 ], point [0 ]),
306+ ],
307+ ]
308+ return bounds
309+
280310class TopoJson (MacroElement ):
281311 def __init__ (self , data , object_path ):
282312 """
@@ -286,10 +316,13 @@ def __init__(self, data, object_path):
286316 super (TopoJson , self ).__init__ ()
287317 self ._name = 'TopoJson'
288318 if 'read' in dir (data ):
319+ self .embed = True
289320 self .data = data .read ()
290321 elif type (data ) is dict :
322+ self .embed = True
291323 self .data = json .dumps (data )
292324 else :
325+ self .embed = False
293326 self .data = data
294327
295328 self .object_path = object_path
@@ -315,6 +348,38 @@ def render(self, **kwargs):
315348 JavascriptLink ("https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.9/topojson.min.js" ), # noqa
316349 name = 'topojson' )
317350
351+ def _get_self_bounds (self ):
352+ """Computes the bounds of the object itself (not including it's children)
353+ in the form [[lat_min, lon_min], [lat_max, lon_max]]
354+ """
355+ if not self .embed :
356+ raise ValueError ('Cannot compute bounds of non-embedded TopoJSON.' )
357+
358+ data = json .loads (self .data )
359+
360+ xmin ,xmax ,ymin ,ymax = None , None , None , None
361+
362+ for arc in data ['arcs' ]:
363+ x ,y = 0 ,0
364+ for dx , dy in arc :
365+ x += dx
366+ y += dy
367+ xmin = none_min (x , xmin )
368+ xmax = none_max (x , xmax )
369+ ymin = none_min (y , ymin )
370+ ymax = none_max (y , ymax )
371+ return [
372+ [
373+ data ['transform' ]['translate' ][0 ] + data ['transform' ]['scale' ][0 ] * xmin ,
374+ data ['transform' ]['translate' ][1 ] + data ['transform' ]['scale' ][1 ] * ymin ,
375+ ],
376+ [
377+ data ['transform' ]['translate' ][0 ] + data ['transform' ]['scale' ][0 ] * xmax ,
378+ data ['transform' ]['translate' ][1 ] + data ['transform' ]['scale' ][1 ] * ymax ,
379+ ]
380+
381+ ]
382+
318383class ColorScale (MacroElement ):
319384 def __init__ (self , color_domain , color_code , caption = "" ):
320385 """
@@ -344,7 +409,6 @@ def render(self, **kwargs):
344409 JavascriptLink ("https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js" ), # noqa
345410 name = 'd3' )
346411
347-
348412class MarkerCluster (Layer ):
349413 """Adds a MarkerCluster layer on the map."""
350414 def __init__ (self , overlay = True , control = True ):
@@ -569,6 +633,23 @@ def __init__(self, locations, color=None, weight=None,
569633 {% endmacro %}
570634 """ ) # noqa
571635
636+ def _get_self_bounds (self ):
637+ """Computes the bounds of the object itself (not including it's children)
638+ in the form [[lat_min, lon_min], [lat_max, lon_max]]
639+ """
640+ bounds = [[None , None ], [None , None ]]
641+ for point in iter_points (self .data ):
642+ bounds = [
643+ [
644+ none_min (bounds [0 ][0 ], point [0 ]),
645+ none_min (bounds [0 ][1 ], point [1 ]),
646+ ],
647+ [
648+ none_max (bounds [1 ][0 ], point [0 ]),
649+ none_max (bounds [1 ][1 ], point [1 ]),
650+ ],
651+ ]
652+ return bounds
572653
573654class MultiPolyLine (MacroElement ):
574655 def __init__ (self , locations , color = None , weight = None ,
@@ -616,6 +697,23 @@ def __init__(self, locations, color=None, weight=None,
616697 {{this._parent.get_name()}}.addLayer({{this.get_name()}});
617698 {% endmacro %}
618699 """ ) # noqa
700+ def _get_self_bounds (self ):
701+ """Computes the bounds of the object itself (not including it's children)
702+ in the form [[lat_min, lon_min], [lat_max, lon_max]]
703+ """
704+ bounds = [[None , None ], [None , None ]]
705+ for point in iter_points (self .data ):
706+ bounds = [
707+ [
708+ none_min (bounds [0 ][0 ], point [0 ]),
709+ none_min (bounds [0 ][1 ], point [1 ]),
710+ ],
711+ [
712+ none_max (bounds [1 ][0 ], point [0 ]),
713+ none_max (bounds [1 ][1 ], point [1 ]),
714+ ],
715+ ]
716+ return bounds
619717
620718class CustomIcon (Icon ):
621719 def __init__ (self , icon_image , icon_size = None , icon_anchor = None ,
0 commit comments