<commit_before># -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (C) 2012-2018 GEM Foundation
#
# OpenQuake is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# OpenQuake 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with OpenQuake. If not, see <http://www.gnu.org/licenses/>.

"""
:mod:`openquake.hazardlib.calc.stochastic` contains
:func:`stochastic_event_set`.
"""
import sys
import time
import numpy
from openquake.baselib.general import AccumDict
from openquake.baselib.performance import Monitor
from openquake.baselib.python3compat import raise_
from openquake.hazardlib.source.rupture import EBRupture
from openquake.hazardlib.contexts import ContextMaker, FarAwayRupture

TWO16 = 2 ** 16  # 65,536
TWO32 = 2 ** 32  # 4,294,967,296
F64 = numpy.float64
U16 = numpy.uint16
U32 = numpy.uint32
U64 = numpy.uint64

def source_site_noop_filter(srcs):
    for src in srcs:
        yield src, None

source_site_noop_filter.integration_distance = {}

# this is used in acceptance/stochastic_test.py, not in the engine
def stochastic_event_set(sources, source_site_filter=source_site_noop_filter):
    """
    Generates a 'Stochastic Event Set' (that is a collection of earthquake
    ruptures) representing a possible *realization* of the seismicity as
    described by a source model.

    The calculator loops over sources. For each source, it loops over ruptures.
    For each rupture, the number of occurrence is randomly sampled by
    calling
    :meth:`openquake.hazardlib.source.rupture.BaseProbabilisticRupture.sample_number_of_occurrences`

    .. note::
        This calculator is using random numbers. In order to reproduce the
        same results numpy random numbers generator needs to be seeded, see
        http://docs.scipy.org/doc/numpy/reference/generated/numpy.random.seed.html

    :param sources:
        An iterator of seismic sources objects (instances of subclasses
        of :class:`~openquake.hazardlib.source.base.BaseSeismicSource`).
    :param source_site_filter:
        The source filter to use (default noop filter)
    :returns:
        Generator of :class:`~openquake.hazardlib.source.rupture.Rupture`
        objects that are contained in an event set. Some ruptures can be
        missing from it, others can appear one or more times in a row.
    """
    for source, s_sites in source_site_filter(sources):
        try:
            for rupture in source.iter_ruptures():
                [n_occ] = rupture.sample_number_of_occurrences()
                for _ in range(n_occ):
                    yield rupture
        except Exception as err:
            etype, err, tb = sys.exc_info()
            msg = 'An error occurred with source id=%s. Error: %s'
            msg %= (source.source_id, str(err))
            raise_(etype, msg, tb)

# ######################## rupture calculator ############################ #

def sample_ruptures(sources, param, src_filter=source_site_noop_filter,
                    monitor=Monitor()):
    """
    :param sources:
        a sequence of sources of the same group
    :param param:
        a dictionary of additional parameters including rlzs_by_gsim,
        ses_per_logic_tree_path and filter_distance
    :param src_filter:
        a source site filter
    :param monitor:
        monitor instance
    :returns:
        a dictionary with eb_ruptures, num_events, num_ruptures, calc_times
    """
    eb_ruptures = []
    # AccumDict of arrays with 3 elements weight, nsites, calc_time
    calc_times = AccumDict(accum=numpy.zeros(3, numpy.float32))
    # Compute and save stochastic event sets
    cmaker = ContextMaker(param['rlzs_by_gsim'],
                          src_filter.integration_distance,
                          param, monitor)
    num_ses = param['ses_per_logic_tree_path']
    rlzs = numpy.concatenate(list(param['rlzs_by_gsim'].values()))
    for src, sites in src_filter(sources):
        t0 = time.time()
        ebrs = build_eb_ruptures(src, rlzs, num_ses, cmaker, sites)
        n_evs = sum(ebr.multiplicity for ebr in ebrs)
        eb_ruptures.extend(ebrs)
        dt = time.time() - t0
        calc_times[src.id] += numpy.array([n_evs, src.nsites, dt])
    dic = dict(eb_ruptures=eb_ruptures, calc_times=calc_times)
    return dic

def fix_shape(occur, num_rlzs):
    n_occ = numpy.zeros(num_rlzs, U16)
    for nr in range(num_rlzs):
        n_occ[nr] = occur
    return n_occ

def build_eb_ruptures(src, rlzs, num_ses, cmaker, s_sites, rup_n_occ=()):
    """
    :param src: a source object
    :param rlzs: realizations of the source model as numpy.uint16 numbers
    :param num_ses: number of stochastic event sets
    :param cmaker: a ContextMaker instance
    :param s_sites: a (filtered) site collection
    :param rup_n_occ: (rup, n_occ) pairs [inferred from the source]
    :returns: a list of EBRuptures
    """
    # NB: s_sites can be None if cmaker.maximum_distance is False, then
    # the contexts are not computed and the ruptures not filtered
    ebrs = []
    samples = getattr(src, 'samples', 1)
    nr = len(rlzs)
    if rup_n_occ == ():
        # NB: the number of occurrences is very low, << 1, so it is
        # more efficient to filter only the ruptures that occur, i.e.
        # to call sample_ruptures *before* the filtering
        rup_n_occ = src.sample_ruptures(samples, num_ses, cmaker.ir_mon)
    for rup, n_occ in rup_n_occ:
        if cmaker.maximum_distance:
            with cmaker.ctx_mon:
                try:
                    rup.sctx, rup.dctx = cmaker.make_contexts(s_sites, rup)
                    indices = rup.sctx.sids
                except FarAwayRupture:
                    continue
        else:
            indices = ()

        if not hasattr(src, 'samples'):  # full enumeration
            n_occ = fix_shape(n_occ, nr)

        ebr = EBRupture(rup, src.id, src.src_group_id, indices, n_occ)
        ebrs.append(ebr)

    return ebrs
<commit_msg>Minor cleanup<commit_after># -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (C) 2012-2018 GEM Foundation
#
# OpenQuake is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# OpenQuake 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with OpenQuake. If not, see <http://www.gnu.org/licenses/>.

"""
:mod:`openquake.hazardlib.calc.stochastic` contains
:func:`stochastic_event_set`.
"""
import sys
import time
import numpy
from openquake.baselib.general import AccumDict
from openquake.baselib.performance import Monitor
from openquake.baselib.python3compat import raise_
from openquake.hazardlib.source.rupture import EBRupture
from openquake.hazardlib.contexts import ContextMaker, FarAwayRupture

TWO16 = 2 ** 16  # 65,536
TWO32 = 2 ** 32  # 4,294,967,296
F64 = numpy.float64
U16 = numpy.uint16
U32 = numpy.uint32
U64 = numpy.uint64

def source_site_noop_filter(srcs):
    for src in srcs:
        yield src, None

source_site_noop_filter.integration_distance = {}

# this is used in acceptance/stochastic_test.py, not in the engine
def stochastic_event_set(sources, source_site_filter=source_site_noop_filter):
    """
    Generates a 'Stochastic Event Set' (that is a collection of earthquake
    ruptures) representing a possible *realization* of the seismicity as
    described by a source model.

    The calculator loops over sources. For each source, it loops over ruptures.
    For each rupture, the number of occurrence is randomly sampled by
    calling
    :meth:`openquake.hazardlib.source.rupture.BaseProbabilisticRupture.sample_number_of_occurrences`

    .. note::
        This calculator is using random numbers. In order to reproduce the
        same results numpy random numbers generator needs to be seeded, see
        http://docs.scipy.org/doc/numpy/reference/generated/numpy.random.seed.html

    :param sources:
        An iterator of seismic sources objects (instances of subclasses
        of :class:`~openquake.hazardlib.source.base.BaseSeismicSource`).
    :param source_site_filter:
        The source filter to use (default noop filter)
    :returns:
        Generator of :class:`~openquake.hazardlib.source.rupture.Rupture`
        objects that are contained in an event set. Some ruptures can be
        missing from it, others can appear one or more times in a row.
    """
    for source, s_sites in source_site_filter(sources):
        try:
            for rupture in source.iter_ruptures():
                [n_occ] = rupture.sample_number_of_occurrences()
                for _ in range(n_occ):
                    yield rupture
        except Exception as err:
            etype, err, tb = sys.exc_info()
            msg = 'An error occurred with source id=%s. Error: %s'
            msg %= (source.source_id, str(err))
            raise_(etype, msg, tb)

# ######################## rupture calculator ############################ #

def sample_ruptures(sources, param, src_filter=source_site_noop_filter,
                    monitor=Monitor()):
    """
    :param sources:
        a sequence of sources of the same group
    :param param:
        a dictionary of additional parameters including rlzs_by_gsim,
        ses_per_logic_tree_path and filter_distance
    :param src_filter:
        a source site filter
    :param monitor:
        monitor instance
    :returns:
        a dictionary with eb_ruptures, num_events, num_ruptures, calc_times
    """
    eb_ruptures = []
    # AccumDict of arrays with 3 elements weight, nsites, calc_time
    calc_times = AccumDict(accum=numpy.zeros(3, numpy.float32))
    # Compute and save stochastic event sets
    cmaker = ContextMaker(param['rlzs_by_gsim'],
                          src_filter.integration_distance,
                          param, monitor)
    num_ses = param['ses_per_logic_tree_path']
    rlzs = numpy.concatenate(list(param['rlzs_by_gsim'].values()))
    for src, sites in src_filter(sources):
        t0 = time.time()
        ebrs = build_eb_ruptures(src, rlzs, num_ses, cmaker, sites)
        n_evs = sum(ebr.multiplicity for ebr in ebrs)
        eb_ruptures.extend(ebrs)
        dt = time.time() - t0
        calc_times[src.id] += numpy.array([n_evs, src.nsites, dt])
    dic = dict(eb_ruptures=eb_ruptures, calc_times=calc_times)
    return dic

def fix_shape(occur, num_rlzs):
    n_occ = numpy.zeros(num_rlzs, U16)
    for nr in range(num_rlzs):
        n_occ[nr] = occur
    return n_occ

def build_eb_ruptures(src, rlzs, num_ses, cmaker, s_sites, rup_n_occ=()):
    """
    :param src: a source object
    :param rlzs: realizations of the source model as numpy.uint16 numbers
    :param num_ses: number of stochastic event sets
    :param cmaker: a ContextMaker instance
    :param s_sites: a (filtered) site collection
    :param rup_n_occ: (rup, n_occ) pairs [inferred from the source]
    :returns: a list of EBRuptures
    """
    # NB: s_sites can be None if cmaker.maximum_distance is False, then
    # the contexts are not computed and the ruptures not filtered
    ebrs = []
    nr = len(rlzs)
    if rup_n_occ == ():
        # NB: the number of occurrences is very low, << 1, so it is
        # more efficient to filter only the ruptures that occur, i.e.
        # to call sample_ruptures *before* the filtering
        samples = getattr(src, 'samples', 1)
        rup_n_occ = src.sample_ruptures(samples, num_ses, cmaker.ir_mon)
    for rup, n_occ in rup_n_occ:
        if cmaker.maximum_distance:
            with cmaker.ctx_mon:
                try:
                    rup.sctx, rup.dctx = cmaker.make_contexts(s_sites, rup)
                    indices = rup.sctx.sids
                except FarAwayRupture:
                    continue
        else:
            indices = ()

        if len(n_occ) != nr:  # full enumeration
            n_occ = fix_shape(n_occ, nr)

        ebr = EBRupture(rup, src.id, src.src_group_id, indices, n_occ)
        ebrs.append(ebr)

    return ebrs
<|endoftext|>


<commit_before># The Hazard Library
# Copyright (C) 2012-2019 GEM Foundation
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
import os
import pickle
import unittest
import tempfile

import numpy
from shapely import wkt

from openquake.baselib import hdf5
from openquake.hazardlib.site import Site, SiteCollection
from openquake.hazardlib.geo.point import Point

assert_eq = numpy.testing.assert_equal

class SiteModelParam(object):
    def __init__(self):
        self.reference_vs30_value = 1.2
        self.reference_vs30_type = 'measured'
        self.reference_depth_to_1pt0km_per_sec = 3.4
        self.reference_depth_to_2pt5km_per_sec = 5.6
        self.reference_siteclass = b'C'

class SiteTestCase(unittest.TestCase):
    def _assert_creation(self, error=None, **kwargs):
        default_kwargs = {
            'location': Point(10, 20),
            'vs30': 10,
            'vs30measured': False,
            'z1pt0': 20,
            'z2pt5': 30,
        }
        default_kwargs.update(kwargs)
        kwargs = default_kwargs
        if error is not None:
            with self.assertRaises(ValueError) as ar:
                Site(**kwargs)
            self.assertEqual(str(ar.exception), error)
        else:
            site = Site(**kwargs)
            for attr in kwargs:
                self.assertEqual(getattr(site, attr), kwargs[attr])

    def test_wrong_vs30(self):
        error = 'vs30 must be positive'
        self._assert_creation(error=error, vs30=0)
        self._assert_creation(error=error, vs30=-1)

    def test_wrong_z1pt0(self):
        error = 'z1pt0 must be positive'
        self._assert_creation(error=error, z1pt0=0)
        self._assert_creation(error=error, z1pt0=-1)

    def test_wrong_z2pt5(self):
        error = 'z2pt5 must be positive'
        self._assert_creation(error=error, z2pt5=0)
        self._assert_creation(error=error, z2pt5=-1)

    def test_successful_creation(self):
        self._assert_creation()

class SiteCollectionCreationTestCase(unittest.TestCase):
    def test_from_sites(self):
        s1 = Site(location=Point(10, 20, 30),
                  vs30=1.2, vs30measured=True,
                  z1pt0=3.4, z2pt5=5.6)
        s2 = Site(location=Point(-1.2, -3.4, -5.6),
                  vs30=55.4, vs30measured=False,
                  z1pt0=66.7, z2pt5=88.9)
        cll = SiteCollection([s1, s2])
        assert_eq(cll.vs30, [1.2, 55.4])
        assert_eq(cll.vs30measured, [True, False])
        assert_eq(cll.z1pt0, [3.4, 66.7])
        assert_eq(cll.z2pt5, [5.6, 88.9])
        assert_eq(cll.mesh.lons, [10, -1.2])
        assert_eq(cll.mesh.lats, [20, -3.4])
        assert_eq(cll.mesh.depths, [30, -5.6])
        for arr in (cll.vs30, cll.z1pt0, cll.z2pt5):
            self.assertIsInstance(arr, numpy.ndarray)
            self.assertEqual(arr.flags.writeable, False)
            self.assertEqual(arr.dtype, float)
        for arr in (cll.vs30measured,):
            self.assertIsInstance(arr, numpy.ndarray)
            self.assertEqual(arr.flags.writeable, False)
            self.assertEqual(arr.dtype, bool)
        self.assertEqual(len(cll), 2)

        # test serialization to hdf5
        fd, fpath = tempfile.mkstemp(suffix='.hdf5')
        os.close(fd)
        with hdf5.File(fpath, 'w') as f:
            f['folder'] = dict(sitecol=cll, b=[2, 3])
            newcll = f['folder/sitecol']
            self.assertEqual(newcll, cll)
            self.assertEqual(list(f['folder/b']), [2, 3])
        os.remove(fpath)

    def test_from_points(self):
        lons = [10, -1.2]
        lats = [20, -3.4]
        depths = [30, -5.6]
        req_params = 'vs30 vs30measured z1pt0 z2pt5 backarc'.split()
        cll = SiteCollection.from_points(
            lons, lats, depths, SiteModelParam(), req_params)
        assert_eq(cll.vs30, [1.2, 1.2])
        assert_eq(cll.vs30measured, [True, True])
        assert_eq(cll.z1pt0, [3.4, 3.4])
        assert_eq(cll.z2pt5, [5.6, 5.6])
        assert_eq(cll.mesh.lons, [10, -1.1999999999999886])
        assert_eq(cll.mesh.lats, [20, -3.4])
        assert_eq(cll.mesh.depths, [30, -5.6])
        assert_eq(cll.backarc, [False, False])

        for arr in (cll.vs30, cll.z1pt0, cll.z2pt5):
            self.assertIsInstance(arr, numpy.ndarray)
            self.assertEqual(arr.dtype, float)
        for arr in (cll.vs30measured, cll.backarc):
            self.assertIsInstance(arr, numpy.ndarray)
            self.assertEqual(arr.dtype, bool)
        self.assertEqual(len(cll), 2)

        # test split_in_tiles
        tiles = cll.split_in_tiles(0)
        self.assertEqual(len(tiles), 1)

        tiles = cll.split_in_tiles(1)
        self.assertEqual(len(tiles), 1)

        tiles = cll.split_in_tiles(2)
        self.assertEqual(len(tiles), 2)

class SiteCollectionFilterTestCase(unittest.TestCase):
    SITES = [
        Site(location=Point(10, 20, 30), vs30=1.2, vs30measured=True,
             z1pt0=3, z2pt5=5),
        Site(location=Point(11, 12, 13), vs30=55.4, vs30measured=False,
             z1pt0=6, z2pt5=8),
        Site(location=Point(0, 2, 0), vs30=2, vs30measured=True,
             z1pt0=9, z2pt5=17),
        Site(location=Point(1, 1, 3), vs30=4, vs30measured=False,
             z1pt0=22, z2pt5=11)
    ]

    def test_filter(self):
        col = SiteCollection(self.SITES)
        filtered = col.filter(numpy.array([True, False, True, False]))
        arreq = numpy.testing.assert_array_equal
        arreq(filtered.vs30, [1.2, 2])
        arreq(filtered.vs30measured, [True, True])
        arreq(filtered.z1pt0, [3, 9])
        arreq(filtered.z2pt5, [5, 17])
        arreq(filtered.mesh.lons, [10, 0])
        arreq(filtered.mesh.lats, [20, 2])
        arreq(filtered.mesh.depths, [30, 0])
        arreq(filtered.sids, [0, 2])

        filtered = col.filter(numpy.array([False, True, True, True]))
        arreq(filtered.vs30, [55.4, 2, 4])
        arreq(filtered.vs30measured, [False, True, False])
        arreq(filtered.z1pt0, [6, 9, 22])
        arreq(filtered.z2pt5, [8, 17, 11])
        arreq(filtered.mesh.lons, [11, 0, 1])
        arreq(filtered.mesh.lats, [12, 2, 1])
        arreq(filtered.mesh.depths, [13, 0, 3])

        # test serialization to hdf5
        fd, fpath = tempfile.mkstemp(suffix='.hdf5')
        os.close(fd)
        with hdf5.File(fpath, 'w') as f:
            f['sitecol'] = filtered
            saved = f['sitecol']
            self.assertEqual(saved, filtered)
        os.remove(fpath)

    def test_filter_all_out(self):
        col = SiteCollection(self.SITES)
        filtered = col.filter(numpy.zeros(len(self.SITES), bool))
        self.assertIs(filtered, None)

    def test_filter_all_in(self):
        col = SiteCollection(self.SITES)
        filtered = col.filter(numpy.ones(len(self.SITES), bool))
        self.assertIs(filtered, col)

    def test_double_filter(self):
        col = SiteCollection(self.SITES)
        filtered = col.filter(numpy.array([True, False, True, True]))
        filtered2 = filtered.filter(numpy.array([False, True, False]))
        arreq = numpy.testing.assert_array_equal
        arreq(filtered2.vs30, [2])
        arreq(filtered2.vs30measured, [True])
        arreq(filtered2.z1pt0, [9])
        arreq(filtered2.z2pt5, [17])
        arreq(filtered2.mesh.lons, [0])
        arreq(filtered2.mesh.lats, [2])
        arreq(filtered2.mesh.depths, [0])
        filtered2 = filtered.filter(numpy.array([True, False, True]))
        arreq(filtered2.vs30, [1.2, 4.])

    def test_within_region(self):
        region = wkt.loads('POLYGON((0 0, 9 0, 9 9, 0 9, 0 0))')
        col = SiteCollection(self.SITES)
        reducedcol = col.within(region)
        # point (10, 20) is out, point (11, 12) is out, point (0, 2)
        # is on the boundary i.e. out, (1, 1) is in
        self.assertEqual(len(reducedcol), 1)

    def test_split(self):
        col = SiteCollection(self.SITES)
        close_sites, far_sites = col.split(Point(10, 19), distance=200)
        self.assertEqual(len(close_sites), 1)
        self.assertEqual(len(far_sites), 3)

        close_sites, far_sites = col.split(Point(10, 19), distance=0)
        self.assertIsNone(close_sites)
        self.assertEqual(len(far_sites), 4)

        close_sites, far_sites = col.split(Point(10, 19), distance=None)
        self.assertEqual(len(close_sites), 4)
        self.assertIsNone(far_sites)

class WithinBBoxTestCase(unittest.TestCase):
    # to understand this test case it is ESSENTIAL to plot sites and
    # bounding boxes; the code in plot_sites.py can get you started

    @classmethod
    def setUpClass(cls):
        lons = numpy.array([-180, -178, 179, 180, 180], numpy.float32)
        lats = numpy.array([-27, -28, -26, -30, -28], numpy.float32)
        cls.sites = SiteCollection.from_points(lons, lats)

    def test1(self):
        assert_eq(self.sites.within_bbox((-182, -28, -178, -26)), [0])

class SiteCollectionIterTestCase(unittest.TestCase):

    def test(self):
        s1 = Site(location=Point(10, 20),
                  vs30=1.2, vs30measured=True,
                  z1pt0=3.4, z2pt5=5.6)
        s2 = Site(location=Point(-1.2, -3.4),
                  vs30=55.4, vs30measured=False,
                  z1pt0=66.7, z2pt5=88.9)
        cll = SiteCollection([s1, s2])

        cll_sites = list(cll)
        for i, s in enumerate([s1, s2]):
            self.assertEqual(s, cll_sites[i])

    def test_depths_go_to_zero(self):
        # Depth information is zero when a :class:`Site`
        # contains only (lon, lat)
        s1 = Site(location=Point(10, 20),
                  vs30=1.2, vs30measured=True,
                  z1pt0=3.4, z2pt5=5.6)
        s2 = Site(location=Point(-1.2, -3.4),
                  vs30=55.4, vs30measured=False,
                  z1pt0=66.7, z2pt5=88.9)
        cll = SiteCollection([s1, s2])

        cll_sites = list(cll)
        exp_s1 = Site(location=Point(10, 20, 0.0),
                      vs30=1.2, vs30measured=True,
                      z1pt0=3.4, z2pt5=5.6)
        exp_s2 = Site(location=Point(-1.2, -3.4, 0.0),
                      vs30=55.4, vs30measured=False,
                      z1pt0=66.7, z2pt5=88.9)

        for i, s in enumerate([exp_s1, exp_s2]):
            self.assertEqual(s, cll_sites[i])

        # test equality of site collections
        sc = SiteCollection([exp_s1, exp_s2])
        self.assertEqual(cll, sc)

class SitePickleTestCase(unittest.TestCase):
    # Tests for pickling Sites.

    def test_dumps_and_loads(self):
        point = Point(1, 2, 3)
        site1 = Site(point, 760.0, 100.0, 5.0)
        site2 = pickle.loads(pickle.dumps(site1))
        self.assertEqual(site1, site2)
<commit_msg>Fixed site_test.py<commit_after># The Hazard Library
# Copyright (C) 2012-2019 GEM Foundation
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
import os
import pickle
import unittest
import tempfile

import numpy
from shapely import wkt

from openquake.baselib import hdf5
from openquake.hazardlib.site import Site, SiteCollection
from openquake.hazardlib.geo.point import Point

assert_eq = numpy.testing.assert_equal

class SiteModelParam(object):
    def __init__(self):
        self.reference_vs30_value = 1.2
        self.reference_vs30_type = 'measured'
        self.reference_depth_to_1pt0km_per_sec = 3.4
        self.reference_depth_to_2pt5km_per_sec = 5.6
        self.reference_siteclass = b'C'
        self.reference_backarc = False

class SiteTestCase(unittest.TestCase):
    def _assert_creation(self, error=None, **kwargs):
        default_kwargs = {
            'location': Point(10, 20),
            'vs30': 10,
            'vs30measured': False,
            'z1pt0': 20,
            'z2pt5': 30,
        }
        default_kwargs.update(kwargs)
        kwargs = default_kwargs
        if error is not None:
            with self.assertRaises(ValueError) as ar:
                Site(**kwargs)
            self.assertEqual(str(ar.exception), error)
        else:
            site = Site(**kwargs)
            for attr in kwargs:
                self.assertEqual(getattr(site, attr), kwargs[attr])

    def test_wrong_vs30(self):
        error = 'vs30 must be positive'
        self._assert_creation(error=error, vs30=0)
        self._assert_creation(error=error, vs30=-1)

    def test_wrong_z1pt0(self):
        error = 'z1pt0 must be positive'
        self._assert_creation(error=error, z1pt0=0)
        self._assert_creation(error=error, z1pt0=-1)

    def test_wrong_z2pt5(self):
        error = 'z2pt5 must be positive'
        self._assert_creation(error=error, z2pt5=0)
        self._assert_creation(error=error, z2pt5=-1)

    def test_successful_creation(self):
        self._assert_creation()

class SiteCollectionCreationTestCase(unittest.TestCase):
    def test_from_sites(self):
        s1 = Site(location=Point(10, 20, 30),
                  vs30=1.2, vs30measured=True,
                  z1pt0=3.4, z2pt5=5.6)
        s2 = Site(location=Point(-1.2, -3.4, -5.6),
                  vs30=55.4, vs30measured=False,
                  z1pt0=66.7, z2pt5=88.9)
        cll = SiteCollection([s1, s2])
        assert_eq(cll.vs30, [1.2, 55.4])
        assert_eq(cll.vs30measured, [True, False])
        assert_eq(cll.z1pt0, [3.4, 66.7])
        assert_eq(cll.z2pt5, [5.6, 88.9])
        assert_eq(cll.mesh.lons, [10, -1.2])
        assert_eq(cll.mesh.lats, [20, -3.4])
        assert_eq(cll.mesh.depths, [30, -5.6])
        for arr in (cll.vs30, cll.z1pt0, cll.z2pt5):
            self.assertIsInstance(arr, numpy.ndarray)
            self.assertEqual(arr.flags.writeable, False)
            self.assertEqual(arr.dtype, float)
        for arr in (cll.vs30measured,):
            self.assertIsInstance(arr, numpy.ndarray)
            self.assertEqual(arr.flags.writeable, False)
            self.assertEqual(arr.dtype, bool)
        self.assertEqual(len(cll), 2)

        # test serialization to hdf5
        fd, fpath = tempfile.mkstemp(suffix='.hdf5')
        os.close(fd)
        with hdf5.File(fpath, 'w') as f:
            f['folder'] = dict(sitecol=cll, b=[2, 3])
            newcll = f['folder/sitecol']
            self.assertEqual(newcll, cll)
            self.assertEqual(list(f['folder/b']), [2, 3])
        os.remove(fpath)

    def test_from_points(self):
        lons = [10, -1.2]
        lats = [20, -3.4]
        depths = [30, -5.6]
        req_params = 'vs30 vs30measured z1pt0 z2pt5 backarc'.split()
        cll = SiteCollection.from_points(
            lons, lats, depths, SiteModelParam(), req_params)
        assert_eq(cll.vs30, [1.2, 1.2])
        assert_eq(cll.vs30measured, [True, True])
        assert_eq(cll.z1pt0, [3.4, 3.4])
        assert_eq(cll.z2pt5, [5.6, 5.6])
        assert_eq(cll.mesh.lons, [10, -1.1999999999999886])
        assert_eq(cll.mesh.lats, [20, -3.4])
        assert_eq(cll.mesh.depths, [30, -5.6])
        assert_eq(cll.backarc, [False, False])

        for arr in (cll.vs30, cll.z1pt0, cll.z2pt5):
            self.assertIsInstance(arr, numpy.ndarray)
            self.assertEqual(arr.dtype, float)
        for arr in (cll.vs30measured, cll.backarc):
            self.assertIsInstance(arr, numpy.ndarray)
            self.assertEqual(arr.dtype, bool)
        self.assertEqual(len(cll), 2)

        # test split_in_tiles
        tiles = cll.split_in_tiles(0)
        self.assertEqual(len(tiles), 1)

        tiles = cll.split_in_tiles(1)
        self.assertEqual(len(tiles), 1)

        tiles = cll.split_in_tiles(2)
        self.assertEqual(len(tiles), 2)

class SiteCollectionFilterTestCase(unittest.TestCase):
    SITES = [
        Site(location=Point(10, 20, 30), vs30=1.2, vs30measured=True,
             z1pt0=3, z2pt5=5),
        Site(location=Point(11, 12, 13), vs30=55.4, vs30measured=False,
             z1pt0=6, z2pt5=8),
        Site(location=Point(0, 2, 0), vs30=2, vs30measured=True,
             z1pt0=9, z2pt5=17),
        Site(location=Point(1, 1, 3), vs30=4, vs30measured=False,
             z1pt0=22, z2pt5=11)
    ]

    def test_filter(self):
        col = SiteCollection(self.SITES)
        filtered = col.filter(numpy.array([True, False, True, False]))
        arreq = numpy.testing.assert_array_equal
        arreq(filtered.vs30, [1.2, 2])
        arreq(filtered.vs30measured, [True, True])
        arreq(filtered.z1pt0, [3, 9])
        arreq(filtered.z2pt5, [5, 17])
        arreq(filtered.mesh.lons, [10, 0])
        arreq(filtered.mesh.lats, [20, 2])
        arreq(filtered.mesh.depths, [30, 0])
        arreq(filtered.sids, [0, 2])

        filtered = col.filter(numpy.array([False, True, True, True]))
        arreq(filtered.vs30, [55.4, 2, 4])
        arreq(filtered.vs30measured, [False, True, False])
        arreq(filtered.z1pt0, [6, 9, 22])
        arreq(filtered.z2pt5, [8, 17, 11])
        arreq(filtered.mesh.lons, [11, 0, 1])
        arreq(filtered.mesh.lats, [12, 2, 1])
        arreq(filtered.mesh.depths, [13, 0, 3])

        # test serialization to hdf5
        fd, fpath = tempfile.mkstemp(suffix='.hdf5')
        os.close(fd)
        with hdf5.File(fpath, 'w') as f:
            f['sitecol'] = filtered
            saved = f['sitecol']
            self.assertEqual(saved, filtered)
        os.remove(fpath)

    def test_filter_all_out(self):
        col = SiteCollection(self.SITES)
        filtered = col.filter(numpy.zeros(len(self.SITES), bool))
        self.assertIs(filtered, None)

    def test_filter_all_in(self):
        col = SiteCollection(self.SITES)
        filtered = col.filter(numpy.ones(len(self.SITES), bool))
        self.assertIs(filtered, col)

    def test_double_filter(self):
        col = SiteCollection(self.SITES)
        filtered = col.filter(numpy.array([True, False, True, True]))
        filtered2 = filtered.filter(numpy.array([False, True, False]))
        arreq = numpy.testing.assert_array_equal
        arreq(filtered2.vs30, [2])
        arreq(filtered2.vs30measured, [True])
        arreq(filtered2.z1pt0, [9])
        arreq(filtered2.z2pt5, [17])
        arreq(filtered2.mesh.lons, [0])
        arreq(filtered2.mesh.lats, [2])
        arreq(filtered2.mesh.depths, [0])
        filtered2 = filtered.filter(numpy.array([True, False, True]))
        arreq(filtered2.vs30, [1.2, 4.])

    def test_within_region(self):
        region = wkt.loads('POLYGON((0 0, 9 0, 9 9, 0 9, 0 0))')
        col = SiteCollection(self.SITES)
        reducedcol = col.within(region)
        # point (10, 20) is out, point (11, 12) is out, point (0, 2)
        # is on the boundary i.e. out, (1, 1) is in
        self.assertEqual(len(reducedcol), 1)

    def test_split(self):
        col = SiteCollection(self.SITES)
        close_sites, far_sites = col.split(Point(10, 19), distance=200)
        self.assertEqual(len(close_sites), 1)
        self.assertEqual(len(far_sites), 3)

        close_sites, far_sites = col.split(Point(10, 19), distance=0)
        self.assertIsNone(close_sites)
        self.assertEqual(len(far_sites), 4)

        close_sites, far_sites = col.split(Point(10, 19), distance=None)
        self.assertEqual(len(close_sites), 4)
        self.assertIsNone(far_sites)

class WithinBBoxTestCase(unittest.TestCase):
    # to understand this test case it is ESSENTIAL to plot sites and
    # bounding boxes; the code in plot_sites.py can get you started

    @classmethod
    def setUpClass(cls):
        lons = numpy.array([-180, -178, 179, 180, 180], numpy.float32)
        lats = numpy.array([-27, -28, -26, -30, -28], numpy.float32)
        cls.sites = SiteCollection.from_points(lons, lats)

    def test1(self):
        assert_eq(self.sites.within_bbox((-182, -28, -178, -26)), [0])

class SiteCollectionIterTestCase(unittest.TestCase):

    def test(self):
        s1 = Site(location=Point(10, 20),
                  vs30=1.2, vs30measured=True,
                  z1pt0=3.4, z2pt5=5.6)
        s2 = Site(location=Point(-1.2, -3.4),
                  vs30=55.4, vs30measured=False,
                  z1pt0=66.7, z2pt5=88.9)
        cll = SiteCollection([s1, s2])

        cll_sites = list(cll)
        for i, s in enumerate([s1, s2]):
            self.assertEqual(s, cll_sites[i])

    def test_depths_go_to_zero(self):
        # Depth information is zero when a :class:`Site`
        # contains only (lon, lat)
        s1 = Site(location=Point(10, 20),
                  vs30=1.2, vs30measured=True,
                  z1pt0=3.4, z2pt5=5.6)
        s2 = Site(location=Point(-1.2, -3.4),
                  vs30=55.4, vs30measured=False,
                  z1pt0=66.7, z2pt5=88.9)
        cll = SiteCollection([s1, s2])

        cll_sites = list(cll)
        exp_s1 = Site(location=Point(10, 20, 0.0),
                      vs30=1.2, vs30measured=True,
                      z1pt0=3.4, z2pt5=5.6)
        exp_s2 = Site(location=Point(-1.2, -3.4, 0.0),
                      vs30=55.4, vs30measured=False,
                      z1pt0=66.7, z2pt5=88.9)

        for i, s in enumerate([exp_s1, exp_s2]):
            self.assertEqual(s, cll_sites[i])

        # test equality of site collections
        sc = SiteCollection([exp_s1, exp_s2])
        self.assertEqual(cll, sc)

class SitePickleTestCase(unittest.TestCase):
    # Tests for pickling Sites.

    def test_dumps_and_loads(self):
        point = Point(1, 2, 3)
        site1 = Site(point, 760.0, 100.0, 5.0)
        site2 = pickle.loads(pickle.dumps(site1))
        self.assertEqual(site1, site2)
<|endoftext|>


<commit_before># The Hazard Library
# Copyright (C) 2012-2018 GEM Foundation
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
import os
import pickle
import unittest
import tempfile

import numpy
from shapely import wkt

from openquake.baselib import hdf5
from openquake.hazardlib.site import Site, SiteCollection
from openquake.hazardlib.geo.point import Point

assert_eq = numpy.testing.assert_equal

class SiteModelParam(object):
    def __init__(self):
        self.reference_vs30_value = 1.2
        self.reference_vs30_type = 'measured'
        self.reference_depth_to_1pt0km_per_sec = 3.4
        self.reference_depth_to_2pt5km_per_sec = 5.6
        self.reference_backarc = False

class SiteTestCase(unittest.TestCase):
    def _assert_creation(self, error=None, **kwargs):
        default_kwargs = {
            'location': Point(10, 20),
            'vs30': 10,
            'vs30measured': False,
            'z1pt0': 20,
            'z2pt5': 30,
            'backarc': True
        }
        default_kwargs.update(kwargs)
        kwargs = default_kwargs
        if error is not None:
            with self.assertRaises(ValueError) as ar:
                Site(**kwargs)
            self.assertEqual(str(ar.exception), error)
        else:
            site = Site(**kwargs)
            for attr in kwargs:
                self.assertEqual(getattr(site, attr), kwargs[attr])

    def test_wrong_vs30(self):
        error = 'vs30 must be positive'
        self._assert_creation(error=error, vs30=0)
        self._assert_creation(error=error, vs30=-1)

    def test_wrong_z1pt0(self):
        error = 'z1pt0 must be positive'
        self._assert_creation(error=error, z1pt0=0)
        self._assert_creation(error=error, z1pt0=-1)

    def test_wrong_z2pt5(self):
        error = 'z2pt5 must be positive'
        self._assert_creation(error=error, z2pt5=0)
        self._assert_creation(error=error, z2pt5=-1)

    def test_successful_creation(self):
        self._assert_creation()

class SiteCollectionCreationTestCase(unittest.TestCase):
    def test_from_sites(self):
        s1 = Site(location=Point(10, 20, 30),
                  vs30=1.2, vs30measured=True,
                  z1pt0=3.4, z2pt5=5.6, backarc=True)
        s2 = Site(location=Point(-1.2, -3.4, -5.6),
                  vs30=55.4, vs30measured=False,
                  z1pt0=66.7, z2pt5=88.9, backarc=False)
        cll = SiteCollection([s1, s2])
        assert_eq(cll.vs30, [1.2, 55.4])
        assert_eq(cll.vs30measured, [True, False])
        assert_eq(cll.z1pt0, [3.4, 66.7])
        assert_eq(cll.z2pt5, [5.6, 88.9])
        assert_eq(cll.mesh.lons, [10, -1.2])
        assert_eq(cll.mesh.lats, [20, -3.4])
        assert_eq(cll.mesh.depths, [30, -5.6])
        assert_eq(cll.backarc, [True, False])
        for arr in (cll.vs30, cll.z1pt0, cll.z2pt5):
            self.assertIsInstance(arr, numpy.ndarray)
            self.assertEqual(arr.flags.writeable, False)
            self.assertEqual(arr.dtype, float)
        for arr in (cll.vs30measured, cll.backarc):
            self.assertIsInstance(arr, numpy.ndarray)
            self.assertEqual(arr.flags.writeable, False)
            self.assertEqual(arr.dtype, bool)
        self.assertEqual(len(cll), 2)

        # test serialization to hdf5
        fd, fpath = tempfile.mkstemp(suffix='.hdf5')
        os.close(fd)
        with hdf5.File(fpath, 'w') as f:
            f['folder'] = dict(sitecol=cll, b=[2, 3])
            newcll = f['folder/sitecol']
            self.assertEqual(newcll, cll)
            self.assertEqual(list(f['folder/b']), [2, 3])
        os.remove(fpath)

    def test_from_points(self):
        lons = [10, -1.2]
        lats = [20, -3.4]
        depths = [30, -5.6]
        cll = SiteCollection.from_points(
            lons, lats, depths, SiteModelParam())
        assert_eq(cll.vs30, [1.2, 1.2])
        assert_eq(cll.vs30measured, [True, True])
        assert_eq(cll.z1pt0, [3.4, 3.4])
        assert_eq(cll.z2pt5, [5.6, 5.6])
        assert_eq(cll.mesh.lons, [10, -1.1999999999999886])
        assert_eq(cll.mesh.lats, [20, -3.4])
        assert_eq(cll.mesh.depths, [30, -5.6])
        assert_eq(cll.backarc, [False, False])

        for arr in (cll.vs30, cll.z1pt0, cll.z2pt5):
            self.assertIsInstance(arr, numpy.ndarray)
            self.assertEqual(arr.dtype, float)
        for arr in (cll.vs30measured, cll.backarc):
            self.assertIsInstance(arr, numpy.ndarray)
            self.assertEqual(arr.dtype, bool)
        self.assertEqual(len(cll), 2)

        # test split_in_tiles
        tiles = cll.split_in_tiles(0)
        self.assertEqual(len(tiles), 1)

        tiles = cll.split_in_tiles(1)
        self.assertEqual(len(tiles), 1)

        tiles = cll.split_in_tiles(2)
        self.assertEqual(len(tiles), 2)

class SiteCollectionFilterTestCase(unittest.TestCase):
    SITES = [
        Site(location=Point(10, 20, 30), vs30=1.2, vs30measured=True,
             z1pt0=3, z2pt5=5),
        Site(location=Point(11, 12, 13), vs30=55.4, vs30measured=False,
             z1pt0=6, z2pt5=8),
        Site(location=Point(0, 2, 0), vs30=2, vs30measured=True,
             z1pt0=9, z2pt5=17),
        Site(location=Point(1, 1, 3), vs30=4, vs30measured=False,
             z1pt0=22, z2pt5=11)
    ]

    def test_filter(self):
        col = SiteCollection(self.SITES)
        filtered = col.filter(numpy.array([True, False, True, False]))
        arreq = numpy.testing.assert_array_equal
        arreq(filtered.vs30, [1.2, 2])
        arreq(filtered.vs30measured, [True, True])
        arreq(filtered.z1pt0, [3, 9])
        arreq(filtered.z2pt5, [5, 17])
        arreq(filtered.mesh.lons, [10, 0])
        arreq(filtered.mesh.lats, [20, 2])
        arreq(filtered.mesh.depths, [30, 0])
        arreq(filtered.sids, [0, 2])

        filtered = col.filter(numpy.array([False, True, True, True]))
        arreq(filtered.vs30, [55.4, 2, 4])
        arreq(filtered.vs30measured, [False, True, False])
        arreq(filtered.z1pt0, [6, 9, 22])
        arreq(filtered.z2pt5, [8, 17, 11])
        arreq(filtered.mesh.lons, [11, 0, 1])
        arreq(filtered.mesh.lats, [12, 2, 1])
        arreq(filtered.mesh.depths, [13, 0, 3])

        # test serialization to hdf5
        fd, fpath = tempfile.mkstemp(suffix='.hdf5')
        os.close(fd)
        with hdf5.File(fpath, 'w') as f:
            f['sitecol'] = filtered
            saved = f['sitecol']
            self.assertEqual(saved, filtered)
        os.remove(fpath)

    def test_filter_all_out(self):
        col = SiteCollection(self.SITES)
        filtered = col.filter(numpy.zeros(len(self.SITES), bool))
        self.assertIs(filtered, None)

    def test_filter_all_in(self):
        col = SiteCollection(self.SITES)
        filtered = col.filter(numpy.ones(len(self.SITES), bool))
        self.assertIs(filtered, col)

    def test_double_filter(self):
        col = SiteCollection(self.SITES)
        filtered = col.filter(numpy.array([True, False, True, True]))
        filtered2 = filtered.filter(numpy.array([False, True, False]))
        arreq = numpy.testing.assert_array_equal
        arreq(filtered2.vs30, [2])
        arreq(filtered2.vs30measured, [True])
        arreq(filtered2.z1pt0, [9])
        arreq(filtered2.z2pt5, [17])
        arreq(filtered2.mesh.lons, [0])
        arreq(filtered2.mesh.lats, [2])
        arreq(filtered2.mesh.depths, [0])
        arreq(filtered.indices, [0, 2, 3])
        arreq(filtered2.indices, [2])
        filtered2 = filtered.filter(numpy.array([True, False, True]))
        arreq(filtered2.indices, [0, 3])

    def test_within_region(self):
        region = wkt.loads('POLYGON((0 0, 9 0, 9 9, 0 9, 0 0))')
        col = SiteCollection(self.SITES)
        reducedcol = col.within(region)
        # point (10, 20) is out, point (11, 12) is out, point (0, 2)
        # is on the boundary i.e. out, (1, 1) is in
        self.assertEqual(len(reducedcol), 1)

class WithinBBoxTestCase(unittest.TestCase):
    # to understand this test case it is ESSENTIAL to plot sites and
    # bounding boxes; the code in plot_sites.py can get you started

    @classmethod
    def setUpClass(cls):
        lons = numpy.array([-180, -178, 179, 180, 180], numpy.float32)
        lats = numpy.array([-27, -28, -26, -30, -28], numpy.float32)
        cls.sites = SiteCollection.from_points(lons, lats)

    def test1(self):
        assert_eq(self.sites.within_bbox((-182, -28, -178, -26)).sids, [0])

class SiteCollectionIterTestCase(unittest.TestCase):

    def test(self):
        s1 = Site(location=Point(10, 20),
                  vs30=1.2, vs30measured=True,
                  z1pt0=3.4, z2pt5=5.6)
        s2 = Site(location=Point(-1.2, -3.4),
                  vs30=55.4, vs30measured=False,
                  z1pt0=66.7, z2pt5=88.9)
        cll = SiteCollection([s1, s2])

        cll_sites = list(cll)
        for i, s in enumerate([s1, s2]):
            self.assertEqual(s, cll_sites[i])

    def test_depths_go_to_zero(self):
        # Depth information is zero when a :class:`Site`
        # contains only (lon, lat)
        s1 = Site(location=Point(10, 20),
                  vs30=1.2, vs30measured=True,
                  z1pt0=3.4, z2pt5=5.6)
        s2 = Site(location=Point(-1.2, -3.4),
                  vs30=55.4, vs30measured=False,
                  z1pt0=66.7, z2pt5=88.9)
        cll = SiteCollection([s1, s2])

        cll_sites = list(cll)
        exp_s1 = Site(location=Point(10, 20, 0.0),
                      vs30=1.2, vs30measured=True,
                      z1pt0=3.4, z2pt5=5.6)
        exp_s2 = Site(location=Point(-1.2, -3.4, 0.0),
                      vs30=55.4, vs30measured=False,
                      z1pt0=66.7, z2pt5=88.9)

        for i, s in enumerate([exp_s1, exp_s2]):
            self.assertEqual(s, cll_sites[i])

        # test equality of site collections
        sc = SiteCollection([exp_s1, exp_s2])
        self.assertEqual(cll, sc)

class SitePickleTestCase(unittest.TestCase):
    # Tests for pickling Sites.

    def test_dumps_and_loads(self):
        point = Point(1, 2, 3)
        site1 = Site(point, 760.0, True, 100.0, 5.0)
        site2 = pickle.loads(pickle.dumps(site1))
        self.assertEqual(site1, site2)
<commit_msg>Fixed test<commit_after># The Hazard Library
# Copyright (C) 2012-2018 GEM Foundation
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
import os
import pickle
import unittest
import tempfile

import numpy
from shapely import wkt

from openquake.baselib import hdf5
from openquake.hazardlib.site import Site, SiteCollection
from openquake.hazardlib.geo.point import Point

assert_eq = numpy.testing.assert_equal

class SiteModelParam(object):
    def __init__(self):
        self.reference_vs30_value = 1.2
        self.reference_vs30_type = 'measured'
        self.reference_depth_to_1pt0km_per_sec = 3.4
        self.reference_depth_to_2pt5km_per_sec = 5.6
        self.reference_backarc = False

class SiteTestCase(unittest.TestCase):
    def _assert_creation(self, error=None, **kwargs):
        default_kwargs = {
            'location': Point(10, 20),
            'vs30': 10,
            'vs30measured': False,
            'z1pt0': 20,
            'z2pt5': 30,
            'backarc': True
        }
        default_kwargs.update(kwargs)
        kwargs = default_kwargs
        if error is not None:
            with self.assertRaises(ValueError) as ar:
                Site(**kwargs)
            self.assertEqual(str(ar.exception), error)
        else:
            site = Site(**kwargs)
            for attr in kwargs:
                self.assertEqual(getattr(site, attr), kwargs[attr])

    def test_wrong_vs30(self):
        error = 'vs30 must be positive'
        self._assert_creation(error=error, vs30=0)
        self._assert_creation(error=error, vs30=-1)

    def test_wrong_z1pt0(self):
        error = 'z1pt0 must be positive'
        self._assert_creation(error=error, z1pt0=0)
        self._assert_creation(error=error, z1pt0=-1)

    def test_wrong_z2pt5(self):
        error = 'z2pt5 must be positive'
        self._assert_creation(error=error, z2pt5=0)
        self._assert_creation(error=error, z2pt5=-1)

    def test_successful_creation(self):
        self._assert_creation()

class SiteCollectionCreationTestCase(unittest.TestCase):
    def test_from_sites(self):
        s1 = Site(location=Point(10, 20, 30),
                  vs30=1.2, vs30measured=True,
                  z1pt0=3.4, z2pt5=5.6, backarc=True)
        s2 = Site(location=Point(-1.2, -3.4, -5.6),
                  vs30=55.4, vs30measured=False,
                  z1pt0=66.7, z2pt5=88.9, backarc=False)
        cll = SiteCollection([s1, s2])
        assert_eq(cll.vs30, [1.2, 55.4])
        assert_eq(cll.vs30measured, [True, False])
        assert_eq(cll.z1pt0, [3.4, 66.7])
        assert_eq(cll.z2pt5, [5.6, 88.9])
        assert_eq(cll.mesh.lons, [10, -1.2])
        assert_eq(cll.mesh.lats, [20, -3.4])
        assert_eq(cll.mesh.depths, [30, -5.6])
        assert_eq(cll.backarc, [True, False])
        for arr in (cll.vs30, cll.z1pt0, cll.z2pt5):
            self.assertIsInstance(arr, numpy.ndarray)
            self.assertEqual(arr.flags.writeable, False)
            self.assertEqual(arr.dtype, float)
        for arr in (cll.vs30measured, cll.backarc):
            self.assertIsInstance(arr, numpy.ndarray)
            self.assertEqual(arr.flags.writeable, False)
            self.assertEqual(arr.dtype, bool)
        self.assertEqual(len(cll), 2)

        # test serialization to hdf5
        fd, fpath = tempfile.mkstemp(suffix='.hdf5')
        os.close(fd)
        with hdf5.File(fpath, 'w') as f:
            f['folder'] = dict(sitecol=cll, b=[2, 3])
            newcll = f['folder/sitecol']
            self.assertEqual(newcll, cll)
            self.assertEqual(list(f['folder/b']), [2, 3])
        os.remove(fpath)

    def test_from_points(self):
        lons = [10, -1.2]
        lats = [20, -3.4]
        depths = [30, -5.6]
        cll = SiteCollection.from_points(
            lons, lats, depths, SiteModelParam())
        assert_eq(cll.vs30, [1.2, 1.2])
        assert_eq(cll.vs30measured, [True, True])
        assert_eq(cll.z1pt0, [3.4, 3.4])
        assert_eq(cll.z2pt5, [5.6, 5.6])
        assert_eq(cll.mesh.lons, [10, -1.1999999999999886])
        assert_eq(cll.mesh.lats, [20, -3.4])
        assert_eq(cll.mesh.depths, [30, -5.6])
        assert_eq(cll.backarc, [False, False])

        for arr in (cll.vs30, cll.z1pt0, cll.z2pt5):
            self.assertIsInstance(arr, numpy.ndarray)
            self.assertEqual(arr.dtype, float)
        for arr in (cll.vs30measured, cll.backarc):
            self.assertIsInstance(arr, numpy.ndarray)
            self.assertEqual(arr.dtype, bool)
        self.assertEqual(len(cll), 2)

        # test split_in_tiles
        tiles = cll.split_in_tiles(0)
        self.assertEqual(len(tiles), 1)

        tiles = cll.split_in_tiles(1)
        self.assertEqual(len(tiles), 1)

        tiles = cll.split_in_tiles(2)
        self.assertEqual(len(tiles), 2)

class SiteCollectionFilterTestCase(unittest.TestCase):
    SITES = [
        Site(location=Point(10, 20, 30), vs30=1.2, vs30measured=True,
             z1pt0=3, z2pt5=5),
        Site(location=Point(11, 12, 13), vs30=55.4, vs30measured=False,
             z1pt0=6, z2pt5=8),
        Site(location=Point(0, 2, 0), vs30=2, vs30measured=True,
             z1pt0=9, z2pt5=17),
        Site(location=Point(1, 1, 3), vs30=4, vs30measured=False,
             z1pt0=22, z2pt5=11)
    ]

    def test_filter(self):
        col = SiteCollection(self.SITES)
        filtered = col.filter(numpy.array([True, False, True, False]))
        arreq = numpy.testing.assert_array_equal
        arreq(filtered.vs30, [1.2, 2])
        arreq(filtered.vs30measured, [True, True])
        arreq(filtered.z1pt0, [3, 9])
        arreq(filtered.z2pt5, [5, 17])
        arreq(filtered.mesh.lons, [10, 0])
        arreq(filtered.mesh.lats, [20, 2])
        arreq(filtered.mesh.depths, [30, 0])
        arreq(filtered.sids, [0, 2])

        filtered = col.filter(numpy.array([False, True, True, True]))
        arreq(filtered.vs30, [55.4, 2, 4])
        arreq(filtered.vs30measured, [False, True, False])
        arreq(filtered.z1pt0, [6, 9, 22])
        arreq(filtered.z2pt5, [8, 17, 11])
        arreq(filtered.mesh.lons, [11, 0, 1])
        arreq(filtered.mesh.lats, [12, 2, 1])
        arreq(filtered.mesh.depths, [13, 0, 3])

        # test serialization to hdf5
        fd, fpath = tempfile.mkstemp(suffix='.hdf5')
        os.close(fd)
        with hdf5.File(fpath, 'w') as f:
            f['sitecol'] = filtered
            saved = f['sitecol']
            self.assertEqual(saved, filtered)
        os.remove(fpath)

    def test_filter_all_out(self):
        col = SiteCollection(self.SITES)
        filtered = col.filter(numpy.zeros(len(self.SITES), bool))
        self.assertIs(filtered, None)

    def test_filter_all_in(self):
        col = SiteCollection(self.SITES)
        filtered = col.filter(numpy.ones(len(self.SITES), bool))
        self.assertIs(filtered, col)

    def test_double_filter(self):
        col = SiteCollection(self.SITES)
        filtered = col.filter(numpy.array([True, False, True, True]))
        filtered2 = filtered.filter(numpy.array([False, True, False]))
        arreq = numpy.testing.assert_array_equal
        arreq(filtered2.vs30, [2])
        arreq(filtered2.vs30measured, [True])
        arreq(filtered2.z1pt0, [9])
        arreq(filtered2.z2pt5, [17])
        arreq(filtered2.mesh.lons, [0])
        arreq(filtered2.mesh.lats, [2])
        arreq(filtered2.mesh.depths, [0])
        arreq(filtered.indices, [0, 2, 3])
        arreq(filtered2.indices, [2])
        filtered2 = filtered.filter(numpy.array([True, False, True]))
        arreq(filtered2.indices, [0, 3])

    def test_within_region(self):
        region = wkt.loads('POLYGON((0 0, 9 0, 9 9, 0 9, 0 0))')
        col = SiteCollection(self.SITES)
        reducedcol = col.within(region)
        # point (10, 20) is out, point (11, 12) is out, point (0, 2)
        # is on the boundary i.e. out, (1, 1) is in
        self.assertEqual(len(reducedcol), 1)

class WithinBBoxTestCase(unittest.TestCase):
    # to understand this test case it is ESSENTIAL to plot sites and
    # bounding boxes; the code in plot_sites.py can get you started

    @classmethod
    def setUpClass(cls):
        lons = numpy.array([-180, -178, 179, 180, 180], numpy.float32)
        lats = numpy.array([-27, -28, -26, -30, -28], numpy.float32)
        cls.sites = SiteCollection.from_points(lons, lats)

    def test1(self):
        assert_eq(self.sites.within_bbox((-182, -28, -178, -26)), [0])

class SiteCollectionIterTestCase(unittest.TestCase):

    def test(self):
        s1 = Site(location=Point(10, 20),
                  vs30=1.2, vs30measured=True,
                  z1pt0=3.4, z2pt5=5.6)
        s2 = Site(location=Point(-1.2, -3.4),
                  vs30=55.4, vs30measured=False,
                  z1pt0=66.7, z2pt5=88.9)
        cll = SiteCollection([s1, s2])

        cll_sites = list(cll)
        for i, s in enumerate([s1, s2]):
            self.assertEqual(s, cll_sites[i])

    def test_depths_go_to_zero(self):
        # Depth information is zero when a :class:`Site`
        # contains only (lon, lat)
        s1 = Site(location=Point(10, 20),
                  vs30=1.2, vs30measured=True,
                  z1pt0=3.4, z2pt5=5.6)
        s2 = Site(location=Point(-1.2, -3.4),
                  vs30=55.4, vs30measured=False,
                  z1pt0=66.7, z2pt5=88.9)
        cll = SiteCollection([s1, s2])

        cll_sites = list(cll)
        exp_s1 = Site(location=Point(10, 20, 0.0),
                      vs30=1.2, vs30measured=True,
                      z1pt0=3.4, z2pt5=5.6)
        exp_s2 = Site(location=Point(-1.2, -3.4, 0.0),
                      vs30=55.4, vs30measured=False,
                      z1pt0=66.7, z2pt5=88.9)

        for i, s in enumerate([exp_s1, exp_s2]):
            self.assertEqual(s, cll_sites[i])

        # test equality of site collections
        sc = SiteCollection([exp_s1, exp_s2])
        self.assertEqual(cll, sc)

class SitePickleTestCase(unittest.TestCase):
    # Tests for pickling Sites.

    def test_dumps_and_loads(self):
        point = Point(1, 2, 3)
        site1 = Site(point, 760.0, True, 100.0, 5.0)
        site2 = pickle.loads(pickle.dumps(site1))
        self.assertEqual(site1, site2)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import division

import datetime

from django.conf import settings
from django.core.urlresolvers import reverse
from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404
from django.utils.formats import number_format
from django.utils.translation import ugettext as _
from django.db.models import Q

from treemap.audit import Audit
from treemap.ecobackend import ECOBENEFIT_ERRORS
from treemap.lib import execute_sql
from treemap.models import Tree, MapFeature, User, Favorite

from treemap.lib import format_benefits
from treemap.lib.photo import context_dict_for_photo
from treemap.util import leaf_subclasses

def _photo_upload_share_text(feature, has_tree=False):
    return _("I added a photo of this %s!") % feature.display_name.lower()

def _map_feature_audits(user, instance, feature, filters=None,
                        cudf_filters=None):
    if filters is None:
        filters = []
    if cudf_filters is None:
        cudf_filters = []

    readable_plot_fields = feature.visible_fields(user)

    feature_filter = Q(model=feature.feature_type, model_id=feature.pk,
                       field__in=readable_plot_fields)
    filters.append(feature_filter)

    feature_collection_udfs_filter = Q(
        model__in=feature.visible_collection_udfs_audit_names(user),
        model_id__in=feature.collection_udfs_audit_ids())
    cudf_filters.append(feature_collection_udfs_filter)

    # Seems to be much faster to do three smaller
    # queries here instead of ORing them together
    # (about a 50% inprovement!)
    # TODO: Verify this is still the case now that we are also getting
    # collection udf audits
    iaudit = Audit.objects\
        .filter(instance=instance)\
        .exclude(user=User.system_user())

    audits = []
    for afilter in filters:
        audits += list(iaudit.filter(afilter).order_by('-created')[:5])

    # UDF collection audits have some fields which aren't very useful to show
    udf_collection_exclude_filter = Q(
        field__in=['model_id', 'field_definition'])

    for afilter in cudf_filters:
        audits += list(iaudit.filter(afilter)
                             .exclude(udf_collection_exclude_filter)
                             .order_by('-created')[:5])

    audits = sorted(audits, key=lambda audit: audit.updated, reverse=True)[:5]

    return audits

def _add_eco_benefits_to_context_dict(instance, feature, context):
    FeatureClass = feature.__class__

    if not hasattr(FeatureClass, 'benefits'):
        return

    benefits, basis, error = FeatureClass.benefits\
                                         .benefits_for_object(
                                             instance, feature)

    if error in ECOBENEFIT_ERRORS:
        context[error] = True
    elif benefits:
        context.update(format_benefits(instance, benefits, basis))

def _plot_audits(user, instance, plot):
    fake_tree = Tree(instance=instance)
    tree_visible_fields = fake_tree.visible_fields(user)

    # Get a history of trees that were on this plot
    tree_history = plot.get_tree_history()

    tree_filter = Q(model='Tree',
                    field__in=tree_visible_fields,
                    model_id__in=tree_history)

    tree_delete_filter = Q(model='Tree',
                           action=Audit.Type.Delete,
                           model_id__in=tree_history)

    tree_collection_udfs_audit_names =\
        fake_tree.visible_collection_udfs_audit_names(user)

    tree_collection_udfs_filter = Q(
        model__in=tree_collection_udfs_audit_names,
        model_id__in=Tree.static_collection_udfs_audit_ids(
            (instance,), tree_history, tree_collection_udfs_audit_names))

    filters = [tree_filter, tree_delete_filter]
    cudf_filters = [tree_collection_udfs_filter]

    audits = _map_feature_audits(user, instance, plot, filters, cudf_filters)

    return audits

def _add_audits_to_context(audits, context):
    def _audits_are_in_different_groups(prev_audit, audit):
        if prev_audit is None:
            return True
        elif prev_audit.user_id != audit.user_id:
            return True
        else:
            time_difference = last_audit.created - audit.created
            return time_difference > datetime.timedelta(days=1)

    audit_groups = []
    current_audit_group = None
    last_audit = None

    for audit in audits:
        if _audits_are_in_different_groups(last_audit, audit):
            current_audit_group = {
                'created': audit.created,
                'user': audit.user,
                'audits': []}
            audit_groups.append(current_audit_group)
        current_audit_group['audits'].append(audit)
        last_audit = audit
    # Converting the audit groups to tuples makes the template code cleaner
    context['recent_activity'] = [
        (ag['user'], ag['created'], ag['audits']) for ag in audit_groups]

    if len(audits) > 0:
        context['latest_update'] = audits[0]
    else:
        context['latest_update'] = None

def get_map_feature_or_404(feature_id, instance, type=None):
    if type:
        MapFeatureSubclass = MapFeature.get_subclass(type)
        InstanceMapFeature = instance.scope_model(MapFeatureSubclass)
        return get_object_or_404(InstanceMapFeature, pk=feature_id)

    else:
        InstanceMapFeature = instance.scope_model(MapFeature)
        feature = get_object_or_404(InstanceMapFeature, pk=feature_id)

        # Return the concrete subtype (e.g. Plot), not a general MapFeature
        return feature.cast_to_subtype()

def context_dict_for_plot(request, plot, tree_id=None, **kwargs):
    context = context_dict_for_map_feature(request, plot, **kwargs)

    instance = request.instance
    user = request.user

    if tree_id:
        tree = get_object_or_404(Tree,
                                 instance=instance,
                                 plot=plot,
                                 pk=tree_id)
    else:
        tree = plot.current_tree()

    if tree:
        tree.convert_to_display_units()

    if tree is not None:
        photos = tree.photos()
        # can't send a regular photo qs because the API will
        # serialize this to JSON, which is not supported for qs
        context['photos'] = [context_dict_for_photo(request, photo)
                             for photo in photos]
    else:
        photos = []

    has_tree_diameter = tree is not None and tree.diameter is not None
    has_tree_species_with_code = tree is not None \
        and tree.species is not None and tree.species.otm_code is not None
    has_photo = tree is not None and len(photos) > 0

    total_progress_items = 4
    completed_progress_items = 1  # there is always a plot

    if has_tree_diameter:
        completed_progress_items += 1
    if has_tree_species_with_code:
        completed_progress_items += 1
    if has_photo:
        completed_progress_items += 1

    context['progress_percent'] = int(100 * (
        completed_progress_items / total_progress_items))

    context['progress_messages'] = []
    if not tree:
        context['progress_messages'].append(_('Add a tree'))
    if not has_tree_diameter:
        context['progress_messages'].append(_('Add the diameter'))
    if not has_tree_species_with_code:
        context['progress_messages'].append(_('Add the species'))
    if not has_photo:
        context['progress_messages'].append(_('Add a photo'))

    url_kwargs = {'instance_url_name': instance.url_name,
                  'feature_id': plot.pk}
    if tree:
        url_name = 'add_photo_to_tree'
        url_kwargs = dict(url_kwargs.items() + [('tree_id', tree.pk)])
    else:
        url_name = 'add_photo_to_plot'

    context['upload_photo_endpoint'] = reverse(url_name, kwargs=url_kwargs)

    context['plot'] = plot
    context['has_tree'] = tree is not None
    # Give an empty tree when there is none in order to show tree fields easily
    context['tree'] = tree or Tree(plot=plot, instance=instance)

    context['photo_upload_share_text'] = _photo_upload_share_text(
        plot, tree is not None)

    audits = _plot_audits(user, instance, plot)

    _add_audits_to_context(audits, context)

    _add_share_context(context, request, photos)

    return context

def context_dict_for_resource(request, resource, **kwargs):
    context = context_dict_for_map_feature(request, resource, **kwargs)
    instance = request.instance

    # Give them 2 for adding the resource and answering its questions
    total_progress_items = 3
    completed_progress_items = 2

    photos = resource.photos()
    context['photos'] = [context_dict_for_photo(request, photo)
                         for photo in photos]

    has_photos = len(photos) > 0

    if has_photos:
        completed_progress_items += 1

    context['upload_photo_endpoint'] = reverse(
        'add_photo_to_map_feature',
        kwargs={'instance_url_name': instance.url_name,
                'feature_id': resource.pk})

    context['progress_percent'] = int(100 * (
        completed_progress_items / total_progress_items) + .5)

    context['progress_messages'] = []
    if not has_photos:
        context['progress_messages'].append(_('Add a photo'))

    audits = _map_feature_audits(request.user, request.instance, resource)

    _add_audits_to_context(audits, context)

    _add_share_context(context, request, photos)

    return context

def title_for_map_feature(feature):
    # Cast allows the map feature subclass to handle generating
    # the display name
    feature = feature.cast_to_subtype()

    if feature.is_plot:
        tree = feature.current_tree()
        if tree:
            if tree.species:
                title = tree.species.common_name
            else:
                title = _("Missing Species")
        else:
            title = _("Empty Planting Site")
    else:
        title = feature.display_name

    return title

def context_dict_for_map_feature(request, feature, edit=False):
    context = {}

    if edit:
        if feature.is_plot or getattr(feature, 'is_editable', False):
            context['editmode'] = edit
        else:
            raise PermissionDenied("Cannot edit '%s' objects"
                                   % feature.feature_type)

    instance = request.instance
    if instance.pk != feature.instance_id:
        raise Exception("Invalid instance, does not match map feature")

    feature.instance = instance  # save a DB lookup

    user = request.user
    if user and user.is_authenticated():
        feature.mask_unauthorized_fields(user)
        favorited = Favorite.objects \
            .filter(map_feature=feature, user=user).exists()
    else:
        favorited = False

    feature.convert_to_display_units()

    context.update({
        'feature': feature,
        'feature_type': feature.feature_type,
        'title': title_for_map_feature(feature),
        'address_full': feature.address_full,
        'upload_photo_endpoint': None,
        'photos': None,
        'share': None,
        'favorited': favorited,
        'photo_upload_share_text': _photo_upload_share_text(feature),
    })

    _add_eco_benefits_to_context_dict(instance, feature, context)

    return context

def _add_share_context(context, request, photos):
    if len(photos) > 0:
        photo_url = photos[0].thumbnail.url
    elif context.get('has_tree'):
        photo_url = settings.STATIC_URL + "img/tree.png"
    else:
        photo_url = settings.STATIC_URL + "img/otmLogo126.png"
    photo_url = request.build_absolute_uri(photo_url)

    title = _("%(feature)s on %(treemap)s") % {
        'feature': context['title'],
        'treemap': request.instance.name
    }

    if context.get('benefits_total_currency', 0) > 0:
        description = \
            _("This %(feature)s saves %(currency)s%(amount)s per year.") \
            % {
                'feature': context['title'],
                'currency': context['currency_symbol'],
                'amount': number_format(context['benefits_total_currency'],
                                        decimal_pos=0)
            }
    else:
        description = _("This %(feature)s is mapped on %(treemap)s") % {
            'feature': context['title'],
            'treemap': request.instance.name
        }

    context['share'] = {
        'url': request.build_absolute_uri(),
        'title': title,
        'description': description,
        'image': photo_url,
    }

def set_map_feature_updated_at():
    models = [Model.map_feature_type for Model in
              leaf_subclasses(MapFeature)]
    if not models:
        raise Exception("Could not find any map_feature subclasses")

    models_in = "('%s')" % "','".join(models)

    # For a baseline, pull the most recent change to the MapFeature itself.
    # This depends on the fact that all the MapFeature subclasses share the
    # same id pool and ids do not overlap.
    # NOTE: This MUST be run first. Additional update queries compare
    # dates against the updated_at values set by this statement.
    execute_sql("""
UPDATE treemap_mapfeature
SET updated_at = a.updated_at
FROM (
  SELECT model_id as id, MAX(created) AS updated_at
  FROM treemap_audit
  WHERE treemap_audit.model IN %s
  GROUP BY model_id
) a
    WHERE a.id = treemap_mapfeature.id;""" % models_in)

    # If the tree associated with a MapFeature has been updated more
    # recently than the MapFeature, copy the tree's most recent
    # update date to the MapFeature
    execute_sql("""
UPDATE treemap_mapfeature
SET updated_at = GREATEST(treemap_mapfeature.updated_at, b.updated_at)
FROM (
  SELECT plot_id as id, a.updated_at
  FROM treemap_tree
  JOIN (
    SELECT model_id as tree_id, MAX(created) AS updated_at
    FROM treemap_audit
    WHERE treemap_audit.model = 'Tree'
    GROUP BY model_id
  ) a
  ON a.tree_id = treemap_tree.id
) b
WHERE b.id = treemap_mapfeature.id;""")

    # If a photo associated with a Tree or MapFeature has been updated more
    # recently than the MapFeature, copy the photo's most recent
    # update date to the MapFeature. TreePhoto is a subclass of
    # MapFeaturePhoto so they share the same id range.
    execute_sql("""
UPDATE treemap_mapfeature
SET updated_at = GREATEST(treemap_mapfeature.updated_at, b.updated_at)
FROM (
  SELECT map_feature_id as id, a.updated_at
  FROM treemap_mapfeaturephoto
  JOIN (
    SELECT model_id as photo_id, MAX(created) AS updated_at
    FROM treemap_audit
    WHERE treemap_audit.model in ('TreePhoto', 'MapFeaturePhoto')
    GROUP BY model_id
  ) a
  ON a.photo_id = treemap_mapfeaturephoto.id
) b
WHERE b.id = treemap_mapfeature.id;
""")
<commit_msg>Fix share link<commit_after># -*- coding: utf-8 -*-
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import division

import datetime

from django.conf import settings
from django.core.urlresolvers import reverse
from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404
from django.utils.formats import number_format
from django.utils.translation import ugettext as _
from django.db.models import Q

from treemap.audit import Audit
from treemap.ecobackend import ECOBENEFIT_ERRORS
from treemap.lib import execute_sql
from treemap.models import Tree, MapFeature, User, Favorite

from treemap.lib import format_benefits
from treemap.lib.photo import context_dict_for_photo
from treemap.util import leaf_subclasses

def _photo_upload_share_text(feature, has_tree=False):
    return _("I added a photo of this %s!") % feature.display_name.lower()

def _map_feature_audits(user, instance, feature, filters=None,
                        cudf_filters=None):
    if filters is None:
        filters = []
    if cudf_filters is None:
        cudf_filters = []

    readable_plot_fields = feature.visible_fields(user)

    feature_filter = Q(model=feature.feature_type, model_id=feature.pk,
                       field__in=readable_plot_fields)
    filters.append(feature_filter)

    feature_collection_udfs_filter = Q(
        model__in=feature.visible_collection_udfs_audit_names(user),
        model_id__in=feature.collection_udfs_audit_ids())
    cudf_filters.append(feature_collection_udfs_filter)

    # Seems to be much faster to do three smaller
    # queries here instead of ORing them together
    # (about a 50% inprovement!)
    # TODO: Verify this is still the case now that we are also getting
    # collection udf audits
    iaudit = Audit.objects\
        .filter(instance=instance)\
        .exclude(user=User.system_user())

    audits = []
    for afilter in filters:
        audits += list(iaudit.filter(afilter).order_by('-created')[:5])

    # UDF collection audits have some fields which aren't very useful to show
    udf_collection_exclude_filter = Q(
        field__in=['model_id', 'field_definition'])

    for afilter in cudf_filters:
        audits += list(iaudit.filter(afilter)
                             .exclude(udf_collection_exclude_filter)
                             .order_by('-created')[:5])

    audits = sorted(audits, key=lambda audit: audit.updated, reverse=True)[:5]

    return audits

def _add_eco_benefits_to_context_dict(instance, feature, context):
    FeatureClass = feature.__class__

    if not hasattr(FeatureClass, 'benefits'):
        return

    benefits, basis, error = FeatureClass.benefits\
                                         .benefits_for_object(
                                             instance, feature)

    if error in ECOBENEFIT_ERRORS:
        context[error] = True
    elif benefits:
        context.update(format_benefits(instance, benefits, basis))

def _plot_audits(user, instance, plot):
    fake_tree = Tree(instance=instance)
    tree_visible_fields = fake_tree.visible_fields(user)

    # Get a history of trees that were on this plot
    tree_history = plot.get_tree_history()

    tree_filter = Q(model='Tree',
                    field__in=tree_visible_fields,
                    model_id__in=tree_history)

    tree_delete_filter = Q(model='Tree',
                           action=Audit.Type.Delete,
                           model_id__in=tree_history)

    tree_collection_udfs_audit_names =\
        fake_tree.visible_collection_udfs_audit_names(user)

    tree_collection_udfs_filter = Q(
        model__in=tree_collection_udfs_audit_names,
        model_id__in=Tree.static_collection_udfs_audit_ids(
            (instance,), tree_history, tree_collection_udfs_audit_names))

    filters = [tree_filter, tree_delete_filter]
    cudf_filters = [tree_collection_udfs_filter]

    audits = _map_feature_audits(user, instance, plot, filters, cudf_filters)

    return audits

def _add_audits_to_context(audits, context):
    def _audits_are_in_different_groups(prev_audit, audit):
        if prev_audit is None:
            return True
        elif prev_audit.user_id != audit.user_id:
            return True
        else:
            time_difference = last_audit.created - audit.created
            return time_difference > datetime.timedelta(days=1)

    audit_groups = []
    current_audit_group = None
    last_audit = None

    for audit in audits:
        if _audits_are_in_different_groups(last_audit, audit):
            current_audit_group = {
                'created': audit.created,
                'user': audit.user,
                'audits': []}
            audit_groups.append(current_audit_group)
        current_audit_group['audits'].append(audit)
        last_audit = audit
    # Converting the audit groups to tuples makes the template code cleaner
    context['recent_activity'] = [
        (ag['user'], ag['created'], ag['audits']) for ag in audit_groups]

    if len(audits) > 0:
        context['latest_update'] = audits[0]
    else:
        context['latest_update'] = None

def get_map_feature_or_404(feature_id, instance, type=None):
    if type:
        MapFeatureSubclass = MapFeature.get_subclass(type)
        InstanceMapFeature = instance.scope_model(MapFeatureSubclass)
        return get_object_or_404(InstanceMapFeature, pk=feature_id)

    else:
        InstanceMapFeature = instance.scope_model(MapFeature)
        feature = get_object_or_404(InstanceMapFeature, pk=feature_id)

        # Return the concrete subtype (e.g. Plot), not a general MapFeature
        return feature.cast_to_subtype()

def context_dict_for_plot(request, plot, tree_id=None, **kwargs):
    context = context_dict_for_map_feature(request, plot, **kwargs)

    instance = request.instance
    user = request.user

    if tree_id:
        tree = get_object_or_404(Tree,
                                 instance=instance,
                                 plot=plot,
                                 pk=tree_id)
    else:
        tree = plot.current_tree()

    if tree:
        tree.convert_to_display_units()

    if tree is not None:
        photos = tree.photos()
        # can't send a regular photo qs because the API will
        # serialize this to JSON, which is not supported for qs
        context['photos'] = [context_dict_for_photo(request, photo)
                             for photo in photos]
    else:
        photos = []

    has_tree_diameter = tree is not None and tree.diameter is not None
    has_tree_species_with_code = tree is not None \
        and tree.species is not None and tree.species.otm_code is not None
    has_photo = tree is not None and len(photos) > 0

    total_progress_items = 4
    completed_progress_items = 1  # there is always a plot

    if has_tree_diameter:
        completed_progress_items += 1
    if has_tree_species_with_code:
        completed_progress_items += 1
    if has_photo:
        completed_progress_items += 1

    context['progress_percent'] = int(100 * (
        completed_progress_items / total_progress_items))

    context['progress_messages'] = []
    if not tree:
        context['progress_messages'].append(_('Add a tree'))
    if not has_tree_diameter:
        context['progress_messages'].append(_('Add the diameter'))
    if not has_tree_species_with_code:
        context['progress_messages'].append(_('Add the species'))
    if not has_photo:
        context['progress_messages'].append(_('Add a photo'))

    url_kwargs = {'instance_url_name': instance.url_name,
                  'feature_id': plot.pk}
    if tree:
        url_name = 'add_photo_to_tree'
        url_kwargs = dict(url_kwargs.items() + [('tree_id', tree.pk)])
    else:
        url_name = 'add_photo_to_plot'

    context['upload_photo_endpoint'] = reverse(url_name, kwargs=url_kwargs)

    context['plot'] = plot
    context['has_tree'] = tree is not None
    # Give an empty tree when there is none in order to show tree fields easily
    context['tree'] = tree or Tree(plot=plot, instance=instance)

    context['photo_upload_share_text'] = _photo_upload_share_text(
        plot, tree is not None)

    audits = _plot_audits(user, instance, plot)

    _add_audits_to_context(audits, context)

    _add_share_context(context, request, photos)

    return context

def context_dict_for_resource(request, resource, **kwargs):
    context = context_dict_for_map_feature(request, resource, **kwargs)
    instance = request.instance

    # Give them 2 for adding the resource and answering its questions
    total_progress_items = 3
    completed_progress_items = 2

    photos = resource.photos()
    context['photos'] = [context_dict_for_photo(request, photo)
                         for photo in photos]

    has_photos = len(photos) > 0

    if has_photos:
        completed_progress_items += 1

    context['upload_photo_endpoint'] = reverse(
        'add_photo_to_map_feature',
        kwargs={'instance_url_name': instance.url_name,
                'feature_id': resource.pk})

    context['progress_percent'] = int(100 * (
        completed_progress_items / total_progress_items) + .5)

    context['progress_messages'] = []
    if not has_photos:
        context['progress_messages'].append(_('Add a photo'))

    audits = _map_feature_audits(request.user, request.instance, resource)

    _add_audits_to_context(audits, context)

    _add_share_context(context, request, photos)

    return context

def title_for_map_feature(feature):
    # Cast allows the map feature subclass to handle generating
    # the display name
    feature = feature.cast_to_subtype()

    if feature.is_plot:
        tree = feature.current_tree()
        if tree:
            if tree.species:
                title = tree.species.common_name
            else:
                title = _("Missing Species")
        else:
            title = _("Empty Planting Site")
    else:
        title = feature.display_name

    return title

def context_dict_for_map_feature(request, feature, edit=False):
    context = {}

    if edit:
        if feature.is_plot or getattr(feature, 'is_editable', False):
            context['editmode'] = edit
        else:
            raise PermissionDenied("Cannot edit '%s' objects"
                                   % feature.feature_type)

    instance = request.instance
    if instance.pk != feature.instance_id:
        raise Exception("Invalid instance, does not match map feature")

    feature.instance = instance  # save a DB lookup

    user = request.user
    if user and user.is_authenticated():
        feature.mask_unauthorized_fields(user)
        favorited = Favorite.objects \
            .filter(map_feature=feature, user=user).exists()
    else:
        favorited = False

    feature.convert_to_display_units()

    context.update({
        'feature': feature,
        'feature_type': feature.feature_type,
        'title': title_for_map_feature(feature),
        'address_full': feature.address_full,
        'upload_photo_endpoint': None,
        'photos': None,
        'share': None,
        'favorited': favorited,
        'photo_upload_share_text': _photo_upload_share_text(feature),
    })

    _add_eco_benefits_to_context_dict(instance, feature, context)

    return context

def _add_share_context(context, request, photos):
    if len(photos) > 0:
        photo_url = photos[0].thumbnail.url
    elif context.get('has_tree'):
        photo_url = settings.STATIC_URL + "img/tree.png"
    else:
        photo_url = settings.STATIC_URL + "img/otmLogo126.png"
    photo_url = request.build_absolute_uri(photo_url)

    title = _("%(feature)s on %(treemap)s") % {
        'feature': context['title'],
        'treemap': request.instance.name
    }

    if context.get('benefits_total_currency', 0) > 0:
        description = \
            _("This %(feature)s saves %(currency)s%(amount)s per year.") \
            % {
                'feature': context['title'],
                'currency': context['currency_symbol'],
                'amount': number_format(context['benefits_total_currency'],
                                        decimal_pos=0)
            }
    else:
        description = _("This %(feature)s is mapped on %(treemap)s") % {
            'feature': context['title'],
            'treemap': request.instance.name
        }

    url = reverse('map_feature_detail',
                  kwargs={'instance_url_name': request.instance.url_name,
                          'feature_id': context['feature'].pk})

    context['share'] = {
        'url': request.build_absolute_uri(url),
        'title': title,
        'description': description,
        'image': photo_url,
    }

def set_map_feature_updated_at():
    models = [Model.map_feature_type for Model in
              leaf_subclasses(MapFeature)]
    if not models:
        raise Exception("Could not find any map_feature subclasses")

    models_in = "('%s')" % "','".join(models)

    # For a baseline, pull the most recent change to the MapFeature itself.
    # This depends on the fact that all the MapFeature subclasses share the
    # same id pool and ids do not overlap.
    # NOTE: This MUST be run first. Additional update queries compare
    # dates against the updated_at values set by this statement.
    execute_sql("""
UPDATE treemap_mapfeature
SET updated_at = a.updated_at
FROM (
  SELECT model_id as id, MAX(created) AS updated_at
  FROM treemap_audit
  WHERE treemap_audit.model IN %s
  GROUP BY model_id
) a
    WHERE a.id = treemap_mapfeature.id;""" % models_in)

    # If the tree associated with a MapFeature has been updated more
    # recently than the MapFeature, copy the tree's most recent
    # update date to the MapFeature
    execute_sql("""
UPDATE treemap_mapfeature
SET updated_at = GREATEST(treemap_mapfeature.updated_at, b.updated_at)
FROM (
  SELECT plot_id as id, a.updated_at
  FROM treemap_tree
  JOIN (
    SELECT model_id as tree_id, MAX(created) AS updated_at
    FROM treemap_audit
    WHERE treemap_audit.model = 'Tree'
    GROUP BY model_id
  ) a
  ON a.tree_id = treemap_tree.id
) b
WHERE b.id = treemap_mapfeature.id;""")

    # If a photo associated with a Tree or MapFeature has been updated more
    # recently than the MapFeature, copy the photo's most recent
    # update date to the MapFeature. TreePhoto is a subclass of
    # MapFeaturePhoto so they share the same id range.
    execute_sql("""
UPDATE treemap_mapfeature
SET updated_at = GREATEST(treemap_mapfeature.updated_at, b.updated_at)
FROM (
  SELECT map_feature_id as id, a.updated_at
  FROM treemap_mapfeaturephoto
  JOIN (
    SELECT model_id as photo_id, MAX(created) AS updated_at
    FROM treemap_audit
    WHERE treemap_audit.model in ('TreePhoto', 'MapFeaturePhoto')
    GROUP BY model_id
  ) a
  ON a.photo_id = treemap_mapfeaturephoto.id
) b
WHERE b.id = treemap_mapfeature.id;
""")
<|endoftext|>


<commit_before># Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from openstack.config import loader
from openstack.config.loader import *  # noqa

from os_client_config import cloud_config
from os_client_config import defaults

class OpenStackConfig(loader.OpenStackConfig):

    _cloud_region_class = cloud_config.CloudConfig
    _defaults_module = defaults

    get_one_cloud = loader.OpenStackConfig.get_one
    get_all_clouds = loader.OpenStackConfig.get_all

if __name__ == '__main__':
    config = OpenStackConfig().get_all_clouds()
    for cloud in config:
        print_cloud = False
        if len(sys.argv) == 1:
            print_cloud = True
        elif len(sys.argv) == 3 and (
                sys.argv[1] == cloud.name and sys.argv[2] == cloud.region):
            print_cloud = True
        elif len(sys.argv) == 2 and (
                sys.argv[1] == cloud.name):
            print_cloud = True

        if print_cloud:
            print(cloud.name, cloud.region, cloud.config)
<commit_msg>Add cache methods back to OpenSackConfig<commit_after># Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from openstack.config import loader
from openstack.config.loader import *  # noqa

from os_client_config import cloud_config
from os_client_config import defaults

class OpenStackConfig(loader.OpenStackConfig):

    _cloud_region_class = cloud_config.CloudConfig
    _defaults_module = defaults

    get_one_cloud = loader.OpenStackConfig.get_one
    get_all_clouds = loader.OpenStackConfig.get_all

    def get_cache_expiration_time(self):
        return int(self._cache_expiration_time)

    def get_cache_interval(self):
        return self.get_cache_expiration_time()

    def get_cache_max_age(self):
        return self.get_cache_expiration_time()

    def get_cache_path(self):
        return self._cache_path

    def get_cache_class(self):
        return self._cache_class

    def get_cache_arguments(self):
        return copy.deepcopy(self._cache_arguments)

    def get_cache_expiration(self):
        return copy.deepcopy(self._cache_expiration)

if __name__ == '__main__':
    config = OpenStackConfig().get_all_clouds()
    for cloud in config:
        print_cloud = False
        if len(sys.argv) == 1:
            print_cloud = True
        elif len(sys.argv) == 3 and (
                sys.argv[1] == cloud.name and sys.argv[2] == cloud.region):
            print_cloud = True
        elif len(sys.argv) == 2 and (
                sys.argv[1] == cloud.name):
            print_cloud = True

        if print_cloud:
            print(cloud.name, cloud.region, cloud.config)
<|endoftext|>


<commit_before># -*- mode: python; coding: utf-8 -*-
# Copyright 2016 the HERA Collaboration
# Licensed under the 2-clause BSD license.

"""Testing for `hera_mc.geo_location and geo_handling`.

"""

from __future__ import absolute_import, division, print_function

import unittest

from hera_mc import geo_location, geo_handling, mc, cm_transfer
from hera_mc.tests import TestHERAMC

class TestGeo(TestHERAMC):

    def setUp(self):
        super(TestGeo, self).setUp()

        # Add COFA
        stn = 'cofa'
        prefix = 'COFA'
        st = geo_location.StationType()
        st.station_type_name = stn
        st.prefix = prefix
        self.test_session.add(st)
        self.test_session.commit()
        gl = geo_location.GeoLocation()
        gl.station_name = prefix + '_null'
        gl.station_type_name = stn
        gl.datum = 'WGS84'
        gl.tile = '34J'
        gl.northing = 6601181.0
        gl.easting = 541007.0
        gl.elevation = 1051.69
        gl.created_gpstime = 1172530000
        self.test_session.add(gl)
        self.test_session.commit()

        # Add a test elelment
        self.test_element_stn = 'test_element'
        self.test_element_prefix = 'TE'
        st = geo_location.StationType()
        st.station_type_name = self.test_element_stn
        st.prefix = self.test_element_prefix
        self.test_session.add(st)
        self.test_session.commit()
        gl = geo_location.GeoLocation()
        gl.station_name = self.test_element_prefix + '_ELEMENT'
        self.test_element_station_name = gl.station_name
        gl.station_type_name = self.test_element_stn
        gl.datum = 'WGS84'
        gl.tile = '34J'
        gl.northing = 6601181.0
        gl.easting = 541007.0
        gl.elevation = 1051.69
        gl.created_gpstime = 1172530000
        self.test_session.add(gl)
        self.test_session.commit()

    def test_cofa(self):
        h = geo_handling.Handling(self.test_session)
        cofa = h.cofa()
        self.assertTrue('cofa' in cofa.station_name.lower())

    def test_get_location(self):
        h = geo_handling.Handling(self.test_session)
        located = h.get_location([self.test_element_station_name], 'now')
        self.assertTrue(located[0].station_name == self.test_element_station_name)

    def test_update_new(self):
        nte = self.test_element_prefix + '_new_test_element'
        data = [[nte, 'station_name', nte],
                [nte, 'station_type_name', self.test_element_stn],
                [nte, 'datum', 'WGS84'],
                [nte, 'tile', '34J'],
                [nte, 'northing', 6600000.0],
                [nte, 'easting', 541000.0],
                [nte, 'elevation', 1050.0],
                [nte, 'created_gpstime', 1172530000]]
        geo_location.update(self.test_session, data, add_new_geo=True)
        h = geo_handling.Handling(self.test_session)
        located = h.get_location([nte], 'now')
        self.assertTrue(located[0].station_type_name == self.test_element_stn)

    def test_update_update(self):
        data = [[self.test_element_station_name, 'elevation', 1100.0]]
        geo_location.update(self.test_session, data, add_new_geo=False)
        h = geo_handling.Handling(self.test_session)
        located = h.get_location([self.test_element_station_name], 'now')
        self.assertTrue(located[0].elevation == 1100.0)

if __name__ == '__main__':
    unittest.main()
<commit_msg>added some geo tests<commit_after># -*- mode: python; coding: utf-8 -*-
# Copyright 2016 the HERA Collaboration
# Licensed under the 2-clause BSD license.

"""Testing for `hera_mc.geo_location and geo_handling`.

"""

from __future__ import absolute_import, division, print_function

import unittest

from hera_mc import geo_location, geo_handling, mc, cm_transfer
from hera_mc.tests import TestHERAMC

class TestGeo(TestHERAMC):

    def setUp(self):
        super(TestGeo, self).setUp()

        # Add COFA
        stn = 'cofa'
        prefix = 'COFA'
        st = geo_location.StationType()
        st.station_type_name = stn
        st.prefix = prefix
        self.test_session.add(st)
        self.test_session.commit()
        gl = geo_location.GeoLocation()
        gl.station_name = prefix + '_null'
        gl.station_type_name = stn
        gl.datum = 'WGS84'
        gl.tile = '34J'
        gl.northing = 6601181.0
        gl.easting = 541007.0
        gl.elevation = 1051.69
        gl.created_gpstime = 1172530000
        self.test_session.add(gl)
        self.test_session.commit()

        # Add a test elelment
        self.test_element_stn = 'test_element'
        self.test_element_prefix = 'TE'
        st = geo_location.StationType()
        st.station_type_name = self.test_element_stn
        st.prefix = self.test_element_prefix
        self.test_session.add(st)
        self.test_session.commit()
        gl = geo_location.GeoLocation()
        gl.station_name = self.test_element_prefix + '_ELEMENT'
        self.test_element_station_name = gl.station_name
        gl.station_type_name = self.test_element_stn
        gl.datum = 'WGS84'
        gl.tile = '34J'
        gl.northing = 6601181.0
        gl.easting = 541007.0
        gl.elevation = 1051.69
        gl.created_gpstime = 1172530000
        self.test_session.add(gl)
        self.test_session.commit()

        self.h = geo_handling.Handling(self.test_session)

    def test_cofa(self):
        cofa = self.h.cofa()
        self.assertTrue('cofa' in cofa.station_name.lower())

    def test_get_location(self):
        located = self.h.get_location([self.test_element_station_name], 'now')
        self.assertTrue(located[0].station_name == self.test_element_station_name)

    def test_update_new(self):
        nte = self.test_element_prefix + '_new_test_element'
        data = [[nte, 'station_name', nte],
                [nte, 'station_type_name', self.test_element_stn],
                [nte, 'datum', 'WGS84'],
                [nte, 'tile', '34J'],
                [nte, 'northing', 6600000.0],
                [nte, 'easting', 541000.0],
                [nte, 'elevation', 1050.0],
                [nte, 'created_gpstime', 1172530000]]
        geo_location.update(self.test_session, data, add_new_geo=True)
        located = self.h.get_location([nte], 'now')
        self.assertTrue(located[0].station_type_name == self.test_element_stn)

    def test_update_update(self):
        data = [[self.test_element_station_name, 'elevation', 1100.0]]
        geo_location.update(self.test_session, data, add_new_geo=False)
        located = self.h.get_location([self.test_element_station_name], 'now')
        self.assertTrue(located[0].elevation == 1100.0)

    def test_station_types(self):
        self.h.get_station_types(add_stations=True)
        self.assertTrue(self.h.station_types['COFA']['Name'] == 'cofa')

    def test_is_in_geo_location(self):
        found_it = self.h.is_in_geo_location(self.test_element_station_name)
        self.assertTrue(found_it)

if __name__ == '__main__':
    unittest.main()
<|endoftext|>