<jupyter_start><jupyter_text>Intro tutorialThis document explains how to use the package ``twoaxistracking`` for calculating self-shading of two-axis tracking solar collectors.Import necessary packages:<jupyter_code>import pandas as pd
# The following libraries are not standard and have to be installed seperately.
# It is recommended to install shapely using conda.
from shapely import geometry
import pvlib
import twoaxistracking<jupyter_output><empty_output><jupyter_text>Now, the first step is to define the location/site for where shading is to be calculated. The location is used to determine the solar position in the next steps.<jupyter_code>location = pvlib.location.Location(latitude=54.9788, longitude=12.2666, altitude=100)<jupyter_output><empty_output><jupyter_text>The second step involves deciding on the discrete time-steps for which shading shall be calculated. Generally the time series should cover one year (preferably not a leap year).The most **important parameter is the frequency**, e.g., '1min', '15min', '1hr'.It is also important to set the timezone as this affects the calculation of the solar position. It is recommended to consistently use UTC to avoid mix-ups.<jupyter_code>times = pd.date_range(
    start='2019-1-1 00:00',
    end='2019-12-31 23:59',
    freq='15min',  # Edit the frequecy for a shorter or longer time step
    tz='UTC')<jupyter_output><empty_output><jupyter_text>Next, the solar position is calculated for all of the time time steps using the [`pvlib-python`](https://pvlib-python.readthedocs.io/en/stable/) package:<jupyter_code>df = location.get_solarposition(times)

df.head()  # Show the first 5 lines<jupyter_output><empty_output><jupyter_text>Define the collector aperture geometryIn this step, the solar collector geometry is defined:<jupyter_code># Change these parameters to suit your particular collector aperture
collector_width = 5.697
collector_height = 3.075

collector_geometry = geometry.box(
    -collector_width/2,  # left x-coordinate
    -collector_height/2,  # bottom y-coordinate
    collector_width/2,  # top y-coordinate
    collector_height/2)  # right x-coordinate

collector_geometry<jupyter_output><empty_output><jupyter_text>Similarly, a cirular geometry can be defined as:<jupyter_code>radius = 2
circular_collector = geometry.Point(0, 0).buffer(radius)
circular_collector<jupyter_output><empty_output><jupyter_text>Note, the absolute dimensions do not matter, as the GCR parameter scales the distance between collectors according to the collector area.Derive properties from the collector geometry:<jupyter_code>total_collector_area = collector_geometry.area
# Calculate the miminum distance between collectors
# Note, L_min is also referred to as D_min by some authors
L_min = 2 * collector_geometry.hausdorff_distance(geometry.Point(0, 0))

print("Collector area: %2.1f"% total_collector_area)
print("Collector L_min: %1.2f"% L_min)<jupyter_output>Collector area: 17.5
Collector L_min: 6.47<jupyter_text>Field layout definitionOnce the collector aperture has been determined, the field layout can be defined. It is important to specify the ground cover ratio (GCR), which is the ratio of the collector are to the ground area. Neighbor orderThe neighbor order determines how many collectors to take into account - for a neighbor order of 1 the immidiate 8 collectors are considered, whereas for a neighbor order of 2, 24 shading collectors are considered. It is recommended to use atleast a neighbor order of 2. Standard vs. custom field layoutsIt is possible to choose from four different standard field layouts: `square`, `diagonal`, `hexagon_e_w`, and `hexagon_n_s`.It is also possible to specify a custom layout using the keywords: `aspect ratio`, `offset`, `rotation`, and `gcr`. For a description of the layout parameters, see the paper by [<NAME> (2014)](https://doi.org/10.1016/j.solener.2014.06.012) or check out the function documentation.<jupyter_code>X, Y, Z, tracker_distance, relative_azimuth, relative_slope = \
    twoaxistracking.generate_field_layout(
        gcr=0.3,  # Change this parameter according to your desired density of collectors
        neighbor_order=2,
        layout_type='square',
        L_min=L_min,  # calculated from collector geometry - do not change
        total_collector_area=total_collector_area,  # calculated from collector geometry - do not change
        plot=True)<jupyter_output><empty_output><jupyter_text>Calculate shading fractionNow that the collector geometry and field layout have been defined, it is time to do the actual shading calculations. This step is relatively computational intensive and is mainly affected by the time step, neighbor order, and computational resources available. Typical run times vary between 5 s and 3 min.<jupyter_code>%%time
df['shaded_fraction'] = \
    df.apply(lambda x: twoaxistracking.shaded_fraction(
    solar_azimuth=x['azimuth'],
    solar_elevation=x['elevation'],
    total_collector_geometry=collector_geometry,
    active_collector_geometry=collector_geometry,
    tracker_distance=tracker_distance,
    relative_azimuth=relative_azimuth,
    relative_slope=relative_slope,
    L_min=L_min,
    plot=False),
             axis=1)<jupyter_output>Wall time: 5.55 s<jupyter_text>Visualize the shading fractionPlot the shading fraction for one example day:<jupyter_code>axes = df.loc['2019-06-28':'2019-06-28', ['shaded_fraction','elevation']].plot(subplots=True, ylim=[0,None])<jupyter_output><empty_output><jupyter_text>Visualize the average daily shading fraction:<jupyter_code>df['shaded_fraction'].resample('1d').mean().plot()<jupyter_output><empty_output>