Repository: bokeh/bokeh

Issue:
Add support for `styles` in google map `map_options`
To add a lot of flexibility to the presentation of google maps, it would be nice to support the [styles](https://developers.google.com/maps/documentation/javascript/reference) property in map_options, which is simply some style json (see examples [here](https://snazzymaps.com/explore).  

I may be wrong but this could be a simple fix that checks [here](https://github.com/bokeh/bokeh/blob/master/bokehjs/src/coffee/common/gmap_plot.coffee#L70) to see if anything is set for `styles` and if so, make it part of the map options.  I don't know how or if you'd expose it at this point in the python api, but it'd be nice for properties like this to be able to be passed on to the map call.



Discussion:
@hafen that would be really cool. I think the simplest route would be a property like

```
    styles = String(help='''docs''')
```

with the understanding that string contains valid JSON. A better solution would involve creating a new `JSON` property:

```
    styles = JSON(help='''docs''')
```

that can validate the supplied text as JSON, and perhaps try to auto-convert to JSON if passed something other than a string. 

Edit: If you wanted to try a small simple PR with the `String` approach we could make a separate issue later to change to a `JSON` property

Yes actually a string would be very simple and would probably be the way people would want to specify it if they are building or grabbing styles from sites like snazzymaps.  I'd try a PR but it wouldn't be for at least a few weeks thanks to travel followed by travel followed by travel.


Resolving patch:
diff --git a/bokeh/models/map_plots.py b/bokeh/models/map_plots.py
--- a/bokeh/models/map_plots.py
+++ b/bokeh/models/map_plots.py
@@ -4,7 +4,7 @@
 from __future__ import absolute_import
 
 from ..properties import HasProps
-from ..properties import Int, Float, Enum, Instance
+from ..properties import Enum, Float, Instance, Int, JSON
 from ..enums import MapType
 
 from .plots import Plot
@@ -23,16 +23,22 @@ class GMapOptions(HasProps):
     """)
 
     zoom = Int(12, help="""
-    The initial `zoom level`_ to use when displaying the GMapPlot.
-
-    .. _zoom level: https://developers.google.com/maps/documentation/staticmaps/#Zoomlevels
-
+    The initial zoom level to use when displaying the GMapPlot.
     """)
 
     map_type = Enum(MapType, help="""
     The `map type`_ to use for the GMapPlot.
 
-    .. _map type: https://developers.google.com/maps/documentation/staticmaps/#MapTypes
+    .. _map type: https://developers.google.com/maps/documentation/javascript/reference#MapTypeId
+
+    """)
+
+    styles = JSON(help="""
+    A JSON array of `map styles`_ to use for the GMapPlot. Many example styles can
+    `be found here`_.
+
+    .. _map styles: https://developers.google.com/maps/documentation/javascript/reference#MapTypeStyle
+    .. _be found here: https://snazzymaps.com
 
     """)
 
diff --git a/bokeh/properties.py b/bokeh/properties.py
--- a/bokeh/properties.py
+++ b/bokeh/properties.py
@@ -483,6 +483,26 @@ def validate(self, value):
     def __str__(self):
         return "%s(%r)" % (self.__class__.__name__, self.regex.pattern)
 
+class JSON(String):
+    """ JSON type property validates that text values are valid JSON. 
+    
+    ..  note::
+        The string is transmitted and received by BokehJS as a *string* 
+        containing JSON content. i.e., you must use ``JSON.parse`` to unpack
+        the value into a JavaScript hash. 
+        
+    """
+    def validate(self, value):
+        super(JSON, self).validate(value)
+
+        if value is None: return
+
+        try:
+            import json
+            json.loads(value)
+        except ValueError:
+            raise ValueError("expected JSON text, got %r" % value)
+
 class ParameterizedProperty(Property):
     """ Base class for Properties that have type parameters, e.g.
     ``List(String)``.
diff --git a/examples/glyphs/maps.py b/examples/glyphs/maps.py
--- a/examples/glyphs/maps.py
+++ b/examples/glyphs/maps.py
@@ -14,14 +14,17 @@
 x_range = Range1d()
 y_range = Range1d()
 
-map_options = GMapOptions(lat=30.2861, lng=-97.7394, zoom=15)
+
+# JSON style string taken from: https://snazzymaps.com/style/1/pale-dawn
+map_options = GMapOptions(lat=30.2861, lng=-97.7394, map_type="roadmap", zoom=13, styles="""
+[{"featureType":"administrative","elementType":"all","stylers":[{"visibility":"on"},{"lightness":33}]},{"featureType":"landscape","elementType":"all","stylers":[{"color":"#f2e5d4"}]},{"featureType":"poi.park","elementType":"geometry","stylers":[{"color":"#c5dac6"}]},{"featureType":"poi.park","elementType":"labels","stylers":[{"visibility":"on"},{"lightness":20}]},{"featureType":"road","elementType":"all","stylers":[{"lightness":20}]},{"featureType":"road.highway","elementType":"geometry","stylers":[{"color":"#c5c6c6"}]},{"featureType":"road.arterial","elementType":"geometry","stylers":[{"color":"#e4d7c6"}]},{"featureType":"road.local","elementType":"geometry","stylers":[{"color":"#fbfaf7"}]},{"featureType":"water","elementType":"all","stylers":[{"visibility":"on"},{"color":"#acbcc9"}]}]
+""")
 
 plot = GMapPlot(
     x_range=x_range, y_range=y_range,
     map_options=map_options,
     title = "Austin"
 )
-plot.map_options.map_type="hybrid"
 
 source = ColumnDataSource(
     data=dict(


Test patch: