Exemple #1
0
    def get_max_extent(self):
        """
        Returns the maximum extends of the domain.

        Returns a dictionary with the following keys:
            * minimum_latitude
            * maximum_latitude
            * minimum_longitude
            * maximum_longitude
        """
        return rotations.get_max_extention_of_domain(
            self.min_latitude, self.max_latitude, self.min_longitude,
            self.max_longitude,
            rotation_axis=self.rotation_axis,
            rotation_angle_in_degree=self.rotation_angle_in_degree)
Exemple #2
0
def plot_domain(min_latitude, max_latitude, min_longitude, max_longitude,
                boundary_buffer_in_degree=0.0, rotation_axis=[0.0, 0.0, 1.0],
                rotation_angle_in_degree=0.0, plot_simulation_domain=False,
                zoom=False, resolution=None, ax=None):
    """
    """
    bounds = rotations.get_max_extention_of_domain(
        min_latitude, max_latitude, min_longitude, max_longitude,
        rotation_axis=rotation_axis,
        rotation_angle_in_degree=rotation_angle_in_degree)
    center_lat = bounds["minimum_latitude"] + (
        bounds["maximum_latitude"] - bounds["minimum_latitude"]) / 2.0
    center_lng = bounds["minimum_longitude"] + (
        bounds["maximum_longitude"] - bounds["minimum_longitude"]) / 2.0

    extend_x = bounds["maximum_longitude"] - bounds["minimum_longitude"]
    extend_y = bounds["maximum_latitude"] - bounds["minimum_latitude"]
    max_extend = max(extend_x, extend_y)

    # If the simulation domain is also available, use it to calculate the
    # max_extend. This results in the simulation domain affecting the zoom
    # level.
    if plot_simulation_domain is True:
        simulation_domain = rotations.get_border_latlng_list(
            min_latitude, max_latitude, min_longitude, max_longitude)
        simulation_domain = np.array(simulation_domain)

    # Arbitrary threshold
    if zoom is False or max_extend > 90 or plot_simulation_domain:
        if resolution is None:
            resolution = "c"
        m = Basemap(projection='ortho', lon_0=center_lng, lat_0=center_lat,
                    resolution=resolution, ax=ax)
        stepsize = 10.0
    else:
        if resolution is None:
            resolution = "l"
        # Calculate approximate width and height in meters.
        width = bounds["maximum_longitude"] - bounds["minimum_longitude"]
        height = bounds["maximum_latitude"] - bounds["minimum_latitude"]

        if width > 50.0:
            stepsize = 10.0
        elif 20.0 < width <= 50.0:
            stepsize = 5.0
        elif 5.0 < width <= 20.0:
            stepsize = 2.0
        else:
            stepsize = 1.0

        width *= 110000 * 1.1
        height *= 110000 * 1.3
        # Lambert azimuthal equal area projection. Equal area projections
        # are useful for interpreting features and this particular one also
        # does not distort features a lot on regional scales.
        m = Basemap(projection='laea', resolution=resolution, width=width,
                    height=height, lat_0=center_lat, lon_0=center_lng)

    # Catch warning that no labels can be drawn with an orthographic
    # projection.
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        parallels = np.arange(-90.0, 90.0, stepsize)
        m.drawparallels(parallels, labels=[False, True, False, False],
                        **LINESTYLE)
        meridians = np.arange(0.0, 360.0, stepsize)
        m.drawmeridians(meridians, labels=[False, False, False, True],
                        **LINESTYLE)

    m.drawmapboundary(fill_color='#cccccc')
    m.fillcontinents(color='white', lake_color='#cccccc', zorder=0)
    # m.drawcoastlines()

    border = rotations.get_border_latlng_list(
        min_latitude, max_latitude, min_longitude, max_longitude,
        rotation_axis=rotation_axis,
        rotation_angle_in_degree=rotation_angle_in_degree)
    border = np.array(border)
    lats = border[:, 0]
    lngs = border[:, 1]
    lngs, lats = m(lngs, lats)
    m.plot(lngs, lats, color="black", lw=2, label="Physical Domain")

    if boundary_buffer_in_degree:
        border = rotations.get_border_latlng_list(
            min_latitude + boundary_buffer_in_degree,
            max_latitude - boundary_buffer_in_degree,
            min_longitude + boundary_buffer_in_degree,
            max_longitude - boundary_buffer_in_degree,
            rotation_axis=rotation_axis,
            rotation_angle_in_degree=rotation_angle_in_degree)
        border = np.array(border)
        lats = border[:, 0]
        lngs = border[:, 1]
        lngs, lats = m(lngs, lats)
        m.plot(lngs, lats, color="black", lw=2, alpha=0.4)

    if plot_simulation_domain is True:
        lats = simulation_domain[:, 0]
        lngs = simulation_domain[:, 1]
        lngs, lats = m(lngs, lats)
        m.plot(lngs, lats, color="red", lw=2, label="Simulation Domain")

        if boundary_buffer_in_degree:
            border = rotations.get_border_latlng_list(
                min_latitude + boundary_buffer_in_degree,
                max_latitude - boundary_buffer_in_degree,
                min_longitude + boundary_buffer_in_degree,
                max_longitude - boundary_buffer_in_degree)
            border = np.array(border)
            lats = border[:, 0]
            lngs = border[:, 1]
            lngs, lats = m(lngs, lats)
            m.plot(lngs, lats, color="red", lw=2, alpha=0.4)
        plt.legend()

    plt.gcf().patch.set_alpha(0.0)
    return m
Exemple #3
0
def plot_raydensity(map_object, station_events, min_lat, max_lat, min_lng,
                    max_lng, rot_axis, rot_angle):
    """
    Create a ray-density plot for all events and all stations.

    This function is potentially expensive and will use all CPUs available.
    Does require geographiclib to be installed.
    """
    import ctypes as C
    from lasif.tools.great_circle_binner import GreatCircleBinner
    from lasif.utils import Point
    import multiprocessing
    import progressbar
    from scipy.stats import scoreatpercentile

    bounds = rotations.get_max_extention_of_domain(
        min_lat, max_lat, min_lng, max_lng, rotation_axis=rot_axis,
        rotation_angle_in_degree=rot_angle)

    # Merge everything so that a list with coordinate pairs is created. This
    # list is then distributed among all processors.
    station_event_list = []
    for event, stations in station_events:
        e_point = Point(event["latitude"], event["longitude"])
        for station in stations.itervalues():
            station_event_list.append((e_point, Point(station["latitude"],
                                                      station["longitude"])))

    circle_count = len(station_event_list)

    # The granularity of the latitude/longitude discretization for the
    # raypaths. Attempt to get a somewhat meaningful result in any case.
    lat_lng_count = 1000
    if circle_count < 1000:
        lat_lng_count = 1000
    if circle_count < 10000:
        lat_lng_count = 2000
    else:
        lat_lng_count = 3000

    cpu_count = multiprocessing.cpu_count()

    def to_numpy(raw_array, dtype, shape):
        data = np.frombuffer(raw_array.get_obj())
        data.dtype = dtype
        return data.reshape(shape)

    print "\nLaunching %i greatcircle calculations on %i CPUs..." % \
        (circle_count, cpu_count)

    widgets = ["Progress: ", progressbar.Percentage(),
               progressbar.Bar(), "", progressbar.ETA()]
    pbar = progressbar.ProgressBar(widgets=widgets,
                                   maxval=circle_count).start()

    def great_circle_binning(sta_evs, bin_data_buffer, bin_data_shape,
                             lock, counter):
        new_bins = GreatCircleBinner(
            bounds["minimum_latitude"], bounds["maximum_latitude"],
            lat_lng_count, bounds["minimum_longitude"],
            bounds["maximum_longitude"], lat_lng_count)
        for event, station in sta_evs:
            with lock:
                counter.value += 1
            if not counter.value % 25:
                pbar.update(counter.value)
            new_bins.add_greatcircle(event, station)

        bin_data = to_numpy(bin_data_buffer, np.uint32, bin_data_shape)
        with bin_data_buffer.get_lock():
            bin_data += new_bins.bins

    # Split the data in cpu_count parts.
    def chunk(seq, num):
        avg = len(seq) / float(num)
        out = []
        last = 0.0
        while last < len(seq):
            out.append(seq[int(last):int(last + avg)])
            last += avg
        return out
    chunks = chunk(station_event_list, cpu_count)

    # One instance that collects everything.
    collected_bins = GreatCircleBinner(
        bounds["minimum_latitude"], bounds["maximum_latitude"], lat_lng_count,
        bounds["minimum_longitude"], bounds["maximum_longitude"],
        lat_lng_count)

    # Use a multiprocessing shared memory array and map it to a numpy view.
    collected_bins_data = multiprocessing.Array(C.c_uint32,
                                                collected_bins.bins.size)
    collected_bins.bins = to_numpy(collected_bins_data, np.uint32,
                                   collected_bins.bins.shape)

    # Create, launch and join one process per CPU. Use a shared value as a
    # counter and a lock to avoid race conditions.
    processes = []
    lock = multiprocessing.Lock()
    counter = multiprocessing.Value("i", 0)
    for _i in xrange(cpu_count):
        processes.append(multiprocessing.Process(
            target=great_circle_binning, args=(chunks[_i], collected_bins_data,
                                               collected_bins.bins.shape, lock,
                                               counter)))
    for process in processes:
        process.start()
    for process in processes:
        process.join()

    pbar.finish()

    title = "%i Events with %i recorded 3 component waveforms" % (
        len(station_events), circle_count)
    # plt.gca().set_title(title, size="large")
    plt.title(title, size="xx-large")

    data = collected_bins.bins.transpose()

    if data.max() >= 10:
        data = np.log10(data)
        data += 0.1
        data[np.isinf(data)] = 0.0
        max_val = scoreatpercentile(data.ravel(), 99)
    else:
        max_val = data.max()

    cmap = cm.get_cmap("gist_heat")
    cmap._init()
    cmap._lut[:120, -1] = np.linspace(0, 1.0, 120) ** 2

    # Slightly change the appearance of the map so it suits the rays.
    map_object.drawmapboundary(fill_color='#bbbbbb')
    map_object.fillcontinents(color='#dddddd', lake_color='#dddddd', zorder=0)

    lngs, lats = collected_bins.coordinates
    ln, la = map_object(lngs, lats)
    map_object.pcolormesh(ln, la, data, cmap=cmap, vmin=0, vmax=max_val)
    # Draw the coastlines so they appear over the rays. Otherwise things are
    # sometimes hard to see.
    map_object.drawcoastlines()
    map_object.drawcountries(linewidth=0.2)
    map_object.drawmeridians(np.arange(0, 360, 30), **LINESTYLE)
    map_object.drawparallels(np.arange(-90, 90, 30), **LINESTYLE)
Exemple #4
0
def plot_domain(min_latitude, max_latitude, min_longitude, max_longitude,
        boundary_buffer_in_degree=0.0, rotation_axis=[0.0, 0.0, 1.0],
        rotation_angle_in_degree=0.0, show_plot=True,
        plot_simulation_domain=False, zoom=False, resolution=None):
    """
    """
    bounds = rotations.get_max_extention_of_domain(min_latitude,
        max_latitude, min_longitude, max_longitude,
        rotation_axis=rotation_axis,
        rotation_angle_in_degree=rotation_angle_in_degree)
    center_lat = bounds["minimum_latitude"] + (bounds["maximum_latitude"] -
        bounds["minimum_latitude"]) / 2.0
    center_lng = bounds["minimum_longitude"] + (bounds["maximum_longitude"] -
        bounds["minimum_longitude"]) / 2.0

    extend_x = bounds["maximum_longitude"] - bounds["minimum_longitude"]
    extend_y = bounds["maximum_latitude"] - bounds["minimum_latitude"]
    max_extend = max(extend_x, extend_y)

    # Arbitrary threshold
    if zoom is False or max_extend > 70:
        if resolution is None:
            resolution = "c"
        m = Basemap(projection='ortho', lon_0=center_lng, lat_0=center_lat,
            resolution=resolution)
    else:
        if resolution is None:
            resolution = "l"
        buffer = max_extend * 0.1
        m = Basemap(projection='merc', resolution=resolution,
            llcrnrlat=bounds["minimum_latitude"] - buffer,
            urcrnrlat=bounds["maximum_latitude"] + buffer,
            llcrnrlon=bounds["minimum_longitude"] - buffer,
            urcrnrlon=bounds["maximum_longitude"] + buffer)

    m.drawmapboundary(fill_color='#cccccc')
    m.fillcontinents(color='white', lake_color='#cccccc', zorder=0)

    border = rotations.get_border_latlng_list(min_latitude, max_latitude,
        min_longitude, max_longitude, rotation_axis=rotation_axis,
        rotation_angle_in_degree=rotation_angle_in_degree)
    border = np.array(border)
    lats = border[:, 0]
    lngs = border[:, 1]
    lngs, lats = m(lngs, lats)
    m.plot(lngs, lats, color="black", lw=2, label="Physical Domain")

    if boundary_buffer_in_degree:
        border = rotations.get_border_latlng_list(
            min_latitude + boundary_buffer_in_degree,
            max_latitude - boundary_buffer_in_degree,
            min_longitude + boundary_buffer_in_degree,
            max_longitude - boundary_buffer_in_degree,
            rotation_axis=rotation_axis,
            rotation_angle_in_degree=rotation_angle_in_degree)
        border = np.array(border)
        lats = border[:, 0]
        lngs = border[:, 1]
        lngs, lats = m(lngs, lats)
        m.plot(lngs, lats, color="black", lw=2, alpha=0.4)

    if plot_simulation_domain is True:
        border = rotations.get_border_latlng_list(min_latitude, max_latitude,
            min_longitude, max_longitude)
        border = np.array(border)
        lats = border[:, 0]
        lngs = border[:, 1]
        lngs, lats = m(lngs, lats)
        m.plot(lngs, lats, color="red", lw=2, label="Simulation Domain")

        if boundary_buffer_in_degree:
            border = rotations.get_border_latlng_list(
                min_latitude + boundary_buffer_in_degree,
                max_latitude - boundary_buffer_in_degree,
                min_longitude + boundary_buffer_in_degree,
                max_longitude - boundary_buffer_in_degree)
            border = np.array(border)
            lats = border[:, 0]
            lngs = border[:, 1]
            lngs, lats = m(lngs, lats)
            m.plot(lngs, lats, color="red", lw=2, alpha=0.4)
        plt.legend()

    if show_plot is True:
        plt.show()

    return m
Exemple #5
0
def plot_domain(min_latitude=None, max_latitude=None, min_longitude=None,
                max_longitude=None, boundary_buffer_in_degree=0.0,
                rotation_axis=[0.0, 0.0, 1.0], rotation_angle_in_degree=0.0,
                plot_simulation_domain=False,
                zoom=False, resolution=None, ax=None):
    """
    """
    # If all are None, the domain is considered to be global.
    if [min_latitude, max_latitude, min_longitude, max_longitude] == \
            [None, None, None, None]:
        if resolution is None:
            resolution = "c"
        # Equal area mollweide projection.
        m = Basemap(projection='moll', lon_0=0, resolution=resolution,
                    ax=ax)
        m.drawmapboundary(fill_color='#cccccc')
        m.fillcontinents(color='white', lake_color='#cccccc', zorder=0)
        plt.gcf().patch.set_alpha(0.0)
        return m

    bounds = rotations.get_max_extention_of_domain(
        min_latitude, max_latitude, min_longitude, max_longitude,
        rotation_axis=rotation_axis,
        rotation_angle_in_degree=rotation_angle_in_degree)
    center_lat = bounds["minimum_latitude"] + (
        bounds["maximum_latitude"] - bounds["minimum_latitude"]) / 2.0
    center_lng = bounds["minimum_longitude"] + (
        bounds["maximum_longitude"] - bounds["minimum_longitude"]) / 2.0

    extent_x = bounds["maximum_longitude"] - bounds["minimum_longitude"]
    extent_y = bounds["maximum_latitude"] - bounds["minimum_latitude"]
    max_extent = max(extent_x, extent_y)

    # Arbitrary threshold
    if max_extent > 160:
        buffer = 15
        if resolution is None:
            resolution = "c"
        m = Basemap(lon_0=center_lng,
                    lat_0=center_lat,
                    projection='cea', resolution=resolution,
                    ax=ax,
                    llcrnrlat=bounds["minimum_latitude"] - buffer,
                    urcrnrlat=min(
                        bounds["maximum_latitude"] + buffer * 2, 90.0),
                    urcrnrlon=bounds["maximum_longitude"] + buffer,
                    llcrnrlon=bounds["minimum_longitude"] - buffer)

        # from IPython.core.debugger import Tracer; Tracer(colors="Linux")()
        # m.llcrnrlat = 0

        stepsize = 45.0
    elif zoom is False or max_extent > 90 or plot_simulation_domain:
        if resolution is None:
            resolution = "c"
        m = Basemap(projection='ortho', lon_0=center_lng, lat_0=center_lat,
                    resolution=resolution, ax=ax)
        stepsize = 10.0
    else:
        if resolution is None:
            resolution = "l"
        # Calculate approximate width and height in meters.
        width = bounds["maximum_longitude"] - bounds["minimum_longitude"]
        height = bounds["maximum_latitude"] - bounds["minimum_latitude"]

        if width > 50.0:
            stepsize = 10.0
        elif 20.0 < width <= 50.0:
            stepsize = 5.0
        elif 5.0 < width <= 20.0:
            stepsize = 2.0
        else:
            stepsize = 1.0

        width *= 110000 * 1.1
        height *= 110000 * 1.3
        # Lambert azimuthal equal area projection. Equal area projections
        # are useful for interpreting features and this particular one also
        # does not distort features a lot on regional scales.
        m = Basemap(projection='laea', resolution=resolution, width=width,
                    height=height, lat_0=center_lat, lon_0=center_lng)

    # Catch warning that no labels can be drawn with an orthographic
    # projection.
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        parallels = np.arange(-90.0, 90.0, stepsize)
        m.drawparallels(parallels, labels=[False, True, False, False],
                        **LINESTYLE)
        meridians = np.arange(0.0, 360.0, stepsize)
        m.drawmeridians(meridians, labels=[False, False, False, True],
                        **LINESTYLE)

    m.drawmapboundary(fill_color='#cccccc')
    m.fillcontinents(color='white', lake_color='#cccccc', zorder=0)

    border = rotations.get_border_latlng_list(
        min_latitude, max_latitude, min_longitude, max_longitude,
        rotation_axis=rotation_axis,
        rotation_angle_in_degree=rotation_angle_in_degree)
    border = np.array(border)
    lats = border[:, 0]
    lngs = border[:, 1]
    lngs, lats = m(lngs, lats)
    m.plot(lngs, lats, color="black", lw=2, label="Physical Domain")

    if boundary_buffer_in_degree:
        border = rotations.get_border_latlng_list(
            min_latitude + boundary_buffer_in_degree,
            max_latitude - boundary_buffer_in_degree,
            min_longitude + boundary_buffer_in_degree,
            max_longitude - boundary_buffer_in_degree,
            rotation_axis=rotation_axis,
            rotation_angle_in_degree=rotation_angle_in_degree)
        border = np.array(border)
        lats = border[:, 0]
        lngs = border[:, 1]
        lngs, lats = m(lngs, lats)
        m.plot(lngs, lats, color="black", lw=2, alpha=0.4)

    if plot_simulation_domain is True:
        simulation_domain = rotations.get_border_latlng_list(
            min_latitude, max_latitude, min_longitude, max_longitude)
        simulation_domain = np.array(simulation_domain)

        lats = simulation_domain[:, 0]
        lngs = simulation_domain[:, 1]
        lngs, lats = m(lngs, lats)
        m.plot(lngs, lats, color="red", lw=2, label="Simulation Domain")

        if boundary_buffer_in_degree:
            border = rotations.get_border_latlng_list(
                min_latitude + boundary_buffer_in_degree,
                max_latitude - boundary_buffer_in_degree,
                min_longitude + boundary_buffer_in_degree,
                max_longitude - boundary_buffer_in_degree)
            border = np.array(border)
            lats = border[:, 0]
            lngs = border[:, 1]
            lngs, lats = m(lngs, lats)
            m.plot(lngs, lats, color="red", lw=2, alpha=0.4)
        plt.legend()

    plt.gcf().patch.set_alpha(0.0)
    return m
Exemple #6
0
def plot_domain(min_latitude,
                max_latitude,
                min_longitude,
                max_longitude,
                boundary_buffer_in_degree=0.0,
                rotation_axis=[0.0, 0.0, 1.0],
                rotation_angle_in_degree=0.0,
                show_plot=True,
                plot_simulation_domain=False,
                zoom=False,
                resolution=None):
    """
    """
    bounds = rotations.get_max_extention_of_domain(
        min_latitude,
        max_latitude,
        min_longitude,
        max_longitude,
        rotation_axis=rotation_axis,
        rotation_angle_in_degree=rotation_angle_in_degree)
    center_lat = bounds["minimum_latitude"] + (
        bounds["maximum_latitude"] - bounds["minimum_latitude"]) / 2.0
    center_lng = bounds["minimum_longitude"] + (
        bounds["maximum_longitude"] - bounds["minimum_longitude"]) / 2.0

    extend_x = bounds["maximum_longitude"] - bounds["minimum_longitude"]
    extend_y = bounds["maximum_latitude"] - bounds["minimum_latitude"]
    max_extend = max(extend_x, extend_y)

    # Arbitrary threshold
    if zoom is False or max_extend > 70:
        if resolution is None:
            resolution = "c"
        m = Basemap(projection='ortho',
                    lon_0=center_lng,
                    lat_0=center_lat,
                    resolution=resolution)
    else:
        if resolution is None:
            resolution = "l"
        buffer = max_extend * 0.1
        m = Basemap(projection='merc',
                    resolution=resolution,
                    llcrnrlat=bounds["minimum_latitude"] - buffer,
                    urcrnrlat=bounds["maximum_latitude"] + buffer,
                    llcrnrlon=bounds["minimum_longitude"] - buffer,
                    urcrnrlon=bounds["maximum_longitude"] + buffer)

    m.drawmapboundary(fill_color='#cccccc')
    m.fillcontinents(color='white', lake_color='#cccccc', zorder=0)

    border = rotations.get_border_latlng_list(
        min_latitude,
        max_latitude,
        min_longitude,
        max_longitude,
        rotation_axis=rotation_axis,
        rotation_angle_in_degree=rotation_angle_in_degree)
    border = np.array(border)
    lats = border[:, 0]
    lngs = border[:, 1]
    lngs, lats = m(lngs, lats)
    m.plot(lngs, lats, color="black", lw=2, label="Physical Domain")

    if boundary_buffer_in_degree:
        border = rotations.get_border_latlng_list(
            min_latitude + boundary_buffer_in_degree,
            max_latitude - boundary_buffer_in_degree,
            min_longitude + boundary_buffer_in_degree,
            max_longitude - boundary_buffer_in_degree,
            rotation_axis=rotation_axis,
            rotation_angle_in_degree=rotation_angle_in_degree)
        border = np.array(border)
        lats = border[:, 0]
        lngs = border[:, 1]
        lngs, lats = m(lngs, lats)
        m.plot(lngs, lats, color="black", lw=2, alpha=0.4)

    if plot_simulation_domain is True:
        border = rotations.get_border_latlng_list(min_latitude, max_latitude,
                                                  min_longitude, max_longitude)
        border = np.array(border)
        lats = border[:, 0]
        lngs = border[:, 1]
        lngs, lats = m(lngs, lats)
        m.plot(lngs, lats, color="red", lw=2, label="Simulation Domain")

        if boundary_buffer_in_degree:
            border = rotations.get_border_latlng_list(
                min_latitude + boundary_buffer_in_degree,
                max_latitude - boundary_buffer_in_degree,
                min_longitude + boundary_buffer_in_degree,
                max_longitude - boundary_buffer_in_degree)
            border = np.array(border)
            lats = border[:, 0]
            lngs = border[:, 1]
            lngs, lats = m(lngs, lats)
            m.plot(lngs, lats, color="red", lw=2, alpha=0.4)
        plt.legend()

    if show_plot is True:
        plt.show()

    return m
Exemple #7
0
def plot_raydensity(map_object, station_events, min_lat, max_lat, min_lng,
                    max_lng, rot_axis, rot_angle):
    """
    Create a ray-density plot for all events and all stations.

    This function is potentially expensive and will use all CPUs available.
    Does require geographiclib to be installed.
    """
    import ctypes as C
    from lasif.tools.great_circle_binner import GreatCircleBinner, Point
    import multiprocessing
    import progressbar
    from scipy.stats import scoreatpercentile

    bounds = rotations.get_max_extention_of_domain(
        min_lat,
        max_lat,
        min_lng,
        max_lng,
        rotation_axis=rot_axis,
        rotation_angle_in_degree=rot_angle)

    # Merge everything so that a list with coordinate pairs is created. This
    # list is then distributed among all processors.
    station_event_list = []
    for event, stations in station_events:
        org = event.preferred_origin() or event.origins[0]
        e_point = Point(org.latitude, org.longitude)
        for station in stations.itervalues():
            station_event_list.append(
                (e_point, Point(station["latitude"], station["longitude"])))

    circle_count = len(station_event_list)

    # The granularity of the latitude/longitude discretization for the
    # raypaths. Attempt to get a somewhat meaningful result in any case.
    lat_lng_count = 1000
    if circle_count < 1000:
        lat_lng_count = 1000
    if circle_count < 10000:
        lat_lng_count = 2000
    else:
        lat_lng_count = 3000

    cpu_count = multiprocessing.cpu_count()

    def to_numpy(raw_array, dtype, shape):
        data = np.frombuffer(raw_array.get_obj())
        data.dtype = dtype
        return data.reshape(shape)

    print "\nLaunching %i greatcircle calculations on %i CPUs..." % \
        (circle_count, cpu_count)

    widgets = [
        "Progress: ",
        progressbar.Percentage(),
        progressbar.Bar(), "",
        progressbar.ETA()
    ]
    pbar = progressbar.ProgressBar(widgets=widgets,
                                   maxval=circle_count).start()

    def great_circle_binning(sta_evs, bin_data_buffer, bin_data_shape, lock,
                             counter):
        new_bins = GreatCircleBinner(bounds["minimum_latitude"],
                                     bounds["maximum_latitude"], lat_lng_count,
                                     bounds["minimum_longitude"],
                                     bounds["maximum_longitude"],
                                     lat_lng_count)
        for event, station in sta_evs:
            with lock:
                counter.value += 1
            if not counter.value % 25:
                pbar.update(counter.value)
            new_bins.add_greatcircle(event, station)

        bin_data = to_numpy(bin_data_buffer, np.uint32, bin_data_shape)
        with bin_data_buffer.get_lock():
            bin_data += new_bins.bins

    # Split the data in cpu_count parts.
    def chunk(seq, num):
        avg = len(seq) / float(num)
        out = []
        last = 0.0
        while last < len(seq):
            out.append(seq[int(last):int(last + avg)])
            last += avg
        return out

    chunks = chunk(station_event_list, cpu_count)

    # One instance that collects everything.
    collected_bins = GreatCircleBinner(bounds["minimum_latitude"],
                                       bounds["maximum_latitude"],
                                       lat_lng_count,
                                       bounds["minimum_longitude"],
                                       bounds["maximum_longitude"],
                                       lat_lng_count)

    # Use a multiprocessing shared memory array and map it to a numpy view.
    collected_bins_data = multiprocessing.Array(C.c_uint32,
                                                collected_bins.bins.size)
    collected_bins.bins = to_numpy(collected_bins_data, np.uint32,
                                   collected_bins.bins.shape)

    # Create, launch and join one process per CPU. Use a shared value as a
    # counter and a lock to avoid race conditions.
    processes = []
    lock = multiprocessing.Lock()
    counter = multiprocessing.Value("i", 0)
    for _i in xrange(cpu_count):
        processes.append(
            multiprocessing.Process(target=great_circle_binning,
                                    args=(chunks[_i], collected_bins_data,
                                          collected_bins.bins.shape, lock,
                                          counter)))
    for process in processes:
        process.start()
    for process in processes:
        process.join()

    pbar.finish()

    title = "%i Events with %i recorded 3 component waveforms" % (
        len(station_events), circle_count)
    #plt.gca().set_title(title, size="large")
    plt.title(title, size="xx-large")

    data = collected_bins.bins.transpose()

    if data.max() >= 10:
        data = np.log10(data)
        data += 0.1
        data[np.isinf(data)] = 0.0
        max_val = scoreatpercentile(data.ravel(), 99)
    else:
        max_val = data.max()

    cmap = cm.get_cmap("gist_heat")
    cmap._init()
    cmap._lut[:120, -1] = np.linspace(0, 1.0, 120)**2

    # Slightly change the appearance of the map so it suits the rays.
    map_object.drawmapboundary(fill_color='#bbbbbb')
    map_object.fillcontinents(color='#dddddd', lake_color='#dddddd', zorder=0)

    lngs, lats = collected_bins.coordinates
    ln, la = map_object(lngs, lats)
    map_object.pcolormesh(ln, la, data, cmap=cmap, vmin=0, vmax=max_val)
    # Draw the coastlines so they appear over the rays. Otherwise things are
    # sometimes hard to see.
    map_object.drawcoastlines()
    map_object.drawcountries(linewidth=0.2)
    map_object.drawmeridians(np.arange(0, 360, 30))
    map_object.drawparallels(np.arange(-90, 90, 30))