ANALYSIS OF MCDOT PARKING TICKETS

Our dataset contains all parking citations issued by Montgomery County Department of Transportation (MCDOT) enforcement personnel in the Montgomery County Parking Lot Districts and Transportation Management Districts of Bethesda, Montgomery Hills, Silver Spring, Wheaton, North Bethesda, Friendship Heights, Greater Seneca Science Center and the Residential Permit Parking areas. This includes all on-street metered parking, public surface lots and public garages. The parking citations list the following: date/time, location, vehicle information, and description of the violation.

Data Provider: MCG ESB Service, Montgomery County, MD

Parking Citations Form

Some citations are issued using handheld electronic ticket writing equipment and are downloaded within just a few days after being issued. Handwritten citations can take several weeks to be entered into the parking citation system.

Actual Parking Ticket Integrity of parking citations can be compromised by simple typographical errors, misidentified vehicle or contains a wrong location. For the purposes of this analysis we shall assume each record in the dataset is factual. ****** # EXPLORATORY ANALYSIS This exploratory analysis will use R as the tool to visualize and transform the data in a systematic way. With any dataset there will always be a need to investigate the quality the data.

# Start with clean environment
rm(list=ls());
# Libraries
library(dygraphs); # Interface to JavaScript charting library
library(xts);      # To make the convertion data-frame/xts format
library(tidyverse);# Collection of R packages designed for data science
library(lubridate);# Makes it easier to work with dates and times
library(leaflet);  # Interface to JS library to make interactive web maps 
library(rgdal);    # Geospatial Data Abstraction Library
# Global Variables
infile_raw  <- "./data/DOT_Parking_Tickets.csv";
infile_geo  <- "./data/geocoded.csv";
infile_json <- "./data/json/gz_2010_us_050_00_20m.json";
latBox      <- c( 38.9,  39.4);  # ~Latitudal limits of Montogomery County
lonBox      <- c(-76.9, -77.4);  # ~Longitudal limits of Montogomery County

Data Cleaning

Since the dataset were based on hand written tickets issued by law enforcement and then transcribed into a dataset, there are inconsistencies in the entries (especially when the violation is not one the violations that is pre-populated on the parking ticket form), and missing data. As we go through the data cleaning process we will determine if the data meets our expectations or not.

## Load csv data set
df.raw <- read.csv(infile_raw);
str(df.raw);
'data.frame':   145949 obs. of  14 variables:
 $ Ticket.Number        : int  403678144 402943262 403643236 403454660 402841876 403384332 402419721 403662490 402735911 403345353 ...
 $ Date.Time            : Factor w/ 106861 levels "01/03/2017 01:04:00 PM",..: 40161 95629 37133 24988 86985 19703 58745 38018 79596 17382 ...
 $ Parking.Division     : int  3 3 2 2 2 2 9 2 2 2 ...
 $ Ticket.Location      : Factor w/ 5296 levels "","1 GREENLANE CT",..: 4294 5228 5217 2158 2434 5212 2794 2272 3568 1986 ...
 $ Meter                : Factor w/ 10799 levels "","^()!","0",..: NA 6273 7479 410 622 7000 1 456 10513 648 ...
 $ Violation.Code       : int  35 50 50 50 50 50 54 50 35 50 ...
 $ Violation.Description: Factor w/ 40 levels "","BLK ANTHR VEH - STRE",..: 37 6 6 6 6 6 33 6 37 6 ...
 $ Plate.Year           : Factor w/ 36 levels "","00","0000",..: 22 22 24 22 1 22 21 NA 1 23 ...
 $ Vehicle.Make         : Factor w/ 78 levels "","ACUR","ALFA",..: 28 21 51 12 68 39 17 71 26 24 ...
 $ Plate.Month          : int  7 1 3 11 NA 6 12 NA NA 5 ...
 $ Vehicle.Type         : Factor w/ 16 levels "","2D","4D","BT",..: 3 3 3 16 3 3 16 3 11 16 ...
 $ Vehicle.Color        : Factor w/ 18 levels "","BG","BK","BL",..: 3 3 3 17 8 3 16 15 3 17 ...
 $ Remarks              : Factor w/ 14607 levels "",".",". -NO YEAR STICKER DISPLAYED",..: 14040 1502 1502 314 1502 1502 13733 314 11782 1502 ...
 $ Issuing.Agency       : Factor w/ 1 level "DOT": 1 1 1 1 1 1 1 1 1 1 ...

Elminated three varables: Meter, Issuing.Agency, and Ticket.Number To do data cleaning, you’ll need to deploy all the tools of EDA: visualisation, transformation, and modelling.

## Create a new dataset without missing data
df <- df.raw;                                              # Copy original 
# df$Meter           <- NULL;                                # Remove variable
df$Issuing.Agency  <- NULL;                                # Remove variable
df$Ticket.Number   <- NULL;                                # Remove variable
df$Date.Time       <- mdy_hms(df$Date.Time);               # Convert to ts 
df$Ticket.Location <- as.character(df$Ticket.Location);    # Convert to string
df$Ticket.Date     <- date(df$Date.Time);                  # Derive Date
df$Ticket.Hour     <- hour(df$Date.Time);                  # Derive Hour 
df$Ticket.Day      <- wday(df$Date.Time, label=T, abbr=T); # Derive Day of week
df$Ticket.Month    <- month(df$Date.Time,label=T, abbr=T); # Derive Month
df$Ticket.Year     <- year(df$Date.Time);                  # Derive Year
df$Parking.Division<- factor(case_when(
  is.na(df.raw$Parking.Division) ~ "??", 
  TRUE ~ as.character(df.raw$Parking.Division)
));
df$Violation.Code  <- factor(case_when(
  is.na(df.raw$Violation.Code) ~ as.integer(0),
  TRUE ~ as.integer(df.raw$Violation.Code)
));
df$Violation.Description  <- factor(case_when(
  is.na(df.raw$Violation.Description)                       ~ "No description",
  df.raw$Violation.Description=="UNREG VEH OFF STREET"      ~ "UNREG VEH OFF STREET",           # 02
  df.raw$Violation.Description=="PRK/STND/STOP RUSH H"      ~ "PARK/STAND/STOP - RUSH HOUR",    # 17
  df.raw$Violation.Description=="WITHIN 15 FT HYDRANT"      ~ "WITHIN 15 FEET OF FIRE HYDRANT", # 19
  df.raw$Violation.Description=="NO STND/PRK FIRE LN"       ~ "NO STND/PRK FIRE LN",            # 25
  df.raw$Violation.Description=="NPAT - ON STREET"          ~ "NPAT - ON STREET",               # 34
  df.raw$Violation.Description=="OFFICIAL SIGN OFF ST"      ~ "OFFICIAL SIGN OFF ST",           # 36
  df.raw$Violation.Description=="HANDICAP PKG AREA"         ~ "HANDICAP AREA",                  # 43
  df.raw$Violation.Description=="EXPIRED PRKG METER"        ~ "EXPIRED METER",                  # 50
  df.raw$Violation.Description=="RES PRKG PERMIT ONLY"      ~ "RESIDENTIAL PERMIT ONLY",        # 43
  df.raw$Violation.Description=="NT IN PRK SPC OFF ST"      ~ "NOT WITHIN SPACE - OFF STREET",  # 38
  df.raw$Violation.Description=="COMM. VEH/BUS/REC IN RESID. ZONE" ~ "COMM. VEH/BUS/REC IN RESID. ZONE",      # 04
  df.raw$Violation.Description=="PARKED WITHIN 35 FEET OF INTERSECTION" ~ "WITHIN 35 FEET OF INTERSECTION",   # 06
  df.raw$Violation.Description=="WTHIN 5 FT DRIVEWAY"       ~ "WITHIN 5 FEET OF DRIVEWAY - ON STREET",# 08
  df.raw$Violation.Description=="OFFICIAL SIGN OFF ST"      ~ "OFFICIAL SIGN OFF STREET",       # ??
  df.raw$Violation.Description=="OVR 24 HRS OFF STREE"      ~ "OVER 24 HRS OFF STREET",         # ??
  df.raw$Violation.Description=="UNREGISTERED VEH  ON STR"  ~ "UNREGISTERED VEHICLE ON STREET", # ??
  df.raw$Violation.Description=="WITHIN 20 FT CROSSWK"      ~ "WITHIN 20 FT CROSSWALK",         # ??
  df.raw$Violation.Description=="OVERTM PRKG - STREET"      ~ "OVERTIME PARKING - STREET",      # ??
  df.raw$Violation.Description=="UNREG VEH OFF STREET"      ~ "UNREGISTERED VEHICLE OFF STREET",# ??
  df.raw$Violation.Description=="NO STANDING - ON STR"      ~ "NO STANDING - ON STREET",        # ??
  df.raw$Violation.Description=="DOUBLE PARKED - STRE"      ~ "DOUBLE PARKED - STREET",         # ??
  df.raw$Violation.Description=="IMPEDING TRAFF/SAFET"      ~ "IMPEDING TRAFFIC/SAFETY",        # ??
  df.raw$Violation.Description=="PARK MORE 1 FT CURB"       ~ "PARK MORE THAN 1 FEET FROM CURB",# ??
  df.raw$Violation.Description=="VIOLATION OFFICIAL SIGN  ON STREET" ~ "VIOLATION OFFICIAL SIGN ON STREET",    # ??    
  TRUE ~ as.character(df.raw$Violation.Code)
));
df$Plate.Month     <- factor(case_when(
  df.raw$Plate.Month==1  ~ "JAN", df.raw$Plate.Month==2  ~ "FEB",
  df.raw$Plate.Month==3  ~ "MAR", df.raw$Plate.Month==4  ~ "APR",
  df.raw$Plate.Month==5  ~ "MAY", df.raw$Plate.Month==6  ~ "JUN",
  df.raw$Plate.Month==7  ~ "JUL", df.raw$Plate.Month==8  ~ "AUG",
  df.raw$Plate.Month==9  ~ "SEP", df.raw$Plate.Month==10 ~ "OCT",
  df.raw$Plate.Month==11 ~ "NOV", df.raw$Plate.Month==12 ~ "DEC",
  TRUE ~ "???"
));
df$Plate.Year     <- factor(case_when(
  df.raw$Plate.Year=="00"  | df.raw$Plate.Year=="" | df.raw$Plate.Year=="3540" |
  df.raw$Plate.Year=="NAA" | is.na(df.raw$Plate.Year)  ~ "????",
  df.raw$Plate.Year=="207"  ~ "2007",
  df.raw$Plate.Year=="9"    ~ "2009",
  df.raw$Plate.Year=="2020" ~ "2010",
  df.raw$Plate.Year=="2021" ~ "2011",
  df.raw$Plate.Year=="2022" ~ "2012",
  df.raw$Plate.Year=="2023" ~ "2013",
  df.raw$Plate.Year=="2024" ~ "2014",
  df.raw$Plate.Year=="0216" ~ "2016",
  df.raw$Plate.Year=="17"   | df.raw$Plate.Year=="0217" | df.raw$Plate.Year=="1017" ~ "2017",
  df.raw$Plate.Year=="1018" | df.raw$Plate.Year=="2118" | df.raw$Plate.Year=="2028" ~ "2018",
  df.raw$Plate.Year=="5019" | df.raw$Plate.Year=="0219" ~ "2019",
  TRUE ~ as.character(df.raw$Plate.Year)
));
df$Remarks        <- as.factor(case_when(
  df.raw$Remarks=="1 HR PARKING."             ~ "01 HR PARKING",
  df.raw$Remarks=="1 HR PARKING"              ~ "01 HR PARKING",
  df.raw$Remarks=="2 HR PARKING"              ~ "02 HR PARKING",
  df.raw$Remarks=="3 HR PARKING"              ~ "03 HR PARKING",
  df.raw$Remarks=="4 HR PARKING"              ~ "04 HR PARKING",
  df.raw$Remarks=="9 HR PARKING"              ~ "09 HR PARKING",
  df.raw$Remarks=="9 HR PARKING NO PERMIT"    ~ "09 HR PARKING-NO PERMIT",
  df.raw$Remarks=="9 HR PARKING - NO PERMIT"  ~ "09 HR PARKING-NO PERMIT",
  df.raw$Remarks=="9 HR PARKING/NO PERMIT"    ~ "09 HR PARKING-NO PERMIT",
  df.raw$Remarks=="9 HR PARKING/NO PERIT"     ~ "09 HR PARKING-NO PERMIT",
  df.raw$Remarks=="9 HR PARKING/NO VISIBLE PERMIT"   ~ "09 HR PARKING-NO PERMIT",
  df.raw$Remarks=="9 HR PARKING NO PERMIT INSIDE"    ~ "09 HR PARKING-NO PERMIT",
  df.raw$Remarks=="9 HR PARKING-NO PERMIT DISPLAYED" ~ "09 HR PARKING-NO PERMIT",
  df.raw$Remarks=="10HR PARKING NO PERMIT"    ~ "10 HR PARKING-NO PERMIT",
  df.raw$Remarks=="12 HR PARKING/NO PERMIT"   ~ "12 HR PARKING-NO PERMIT",
  df.raw$Remarks=="12 HR PARKING  NO PERMIT"  ~ "12 HR PARKING-NO PERMIT",
  df.raw$Remarks=="12 HR PARKING NO PERMIT"   ~ "12 HR PARKING-NO PERMIT",
  df.raw$Remarks=="12 HR PARKING - NO PERMIT" ~ "12 HR PARKING-NO PERMIT",
  df.raw$Remarks=="12 HR PARKING NO PERMIT INSIDE"  ~ "12 HR PARKING-NO PERMIT",
  df.raw$Remarks=="15 HR PARKING NO  PERMIT"  ~ "15 HR PARKING-NO PERMIT",
  df.raw$Remarks=="15 HR PARKING NO PERMIT"   ~ "15 HR PARKING-NO PERMIT",
  df.raw$Remarks=="15 HR PARKING/NO PERMIT"   ~ "15 HR PARKING-NO PERMIT",
  TRUE ~ as.character(df.raw$Remarks)
));
df$Vehicle.Color <- as.factor(case_when(
  df.raw$Vehicle.Color=="AL"  ~ "Aluminum",
  df.raw$Vehicle.Color=="AM"  ~ "Amber",
  df.raw$Vehicle.Color=="BG"  ~ "Beige",
  df.raw$Vehicle.Color=="BK"  ~ "Black",
  df.raw$Vehicle.Color=="BL" | df.raw$Vehicle.Color=="BU"  ~ "Blue",
  df.raw$Vehicle.Color=="BN" | df.raw$Vehicle.Color=="BR"  ~ "Brown",
  df.raw$Vehicle.Color=="BZ"  ~ "Bronze",
  df.raw$Vehicle.Color=="CH"  ~ "Charcoal",
  df.raw$Vehicle.Color=="CL"  ~ "Clear",
  df.raw$Vehicle.Color=="DK"  ~ "Dark",
  df.raw$Vehicle.Color=="GD" | df.raw$Vehicle.Color=="GO"  ~ "Gold",
  df.raw$Vehicle.Color=="GN" | df.raw$Vehicle.Color=="GR"  ~ "Green",
  df.raw$Vehicle.Color=="GY"  ~ "Gray",
  df.raw$Vehicle.Color=="GT"  ~ "Granite",
  df.raw$Vehicle.Color=="IV"  ~ "Ivory",
  df.raw$Vehicle.Color=="LT"  ~ "Light",
  df.raw$Vehicle.Color=="MA"  ~ "Magenta",
  df.raw$Vehicle.Color=="MC"  ~ "MC",
  df.raw$Vehicle.Color=="OL"  ~ "Olive",
  df.raw$Vehicle.Color=="OP"  ~ "Opaque",
  df.raw$Vehicle.Color=="OR" | df.raw$Vehicle.Color=="OR"  ~ "Orange",
  df.raw$Vehicle.Color=="PI" | df.raw$Vehicle.Color=="PK"  ~ "Pink",
  df.raw$Vehicle.Color=="PU"  ~ "Purple",
  df.raw$Vehicle.Color=="RD" | df.raw$Vehicle.Color=="RE"  ~ "Red",
  df.raw$Vehicle.Color=="SI"  ~ "Silver",
  df.raw$Vehicle.Color=="SM"  ~ "Smoke",
  df.raw$Vehicle.Color=="TN"  ~ "Tan",
  df.raw$Vehicle.Color=="TK" | df.raw$Vehicle.Color=="TQ"  ~ "Turquoise",
  df.raw$Vehicle.Color=="VT"  ~ "Violet",
  df.raw$Vehicle.Color=="WH" | df.raw$Vehicle.Color=="WT"  ~ "White",
  df.raw$Vehicle.Color=="YE" | df.raw$Vehicle.Color=="YL"  ~ "Yellow",
  TRUE ~ as.character(df.raw$Vehicle.Color)
));
df$Meter <- as.factor(case_when(
  is.na(df.raw$Meter)   ~ as.character("???"),
  df.raw$Meter=="N/A"   ~ as.character("???"),
  df.raw$Meter=="^()!"  ~ as.character("???"),
  TRUE ~ as.character(df.raw$Meter)
));
df <- na.omit(df);
summary(df, maxsum=10);
   Date.Time                   Parking.Division Ticket.Location        Meter       
 Min.   :2016-07-01 06:14:00   ??:    4         Length:145931             : 25127  
 1st Qu.:2016-09-30 13:11:00   0 :    1         Class :character   ???    :  4761  
 Median :2017-01-09 07:59:00   10:  996         Mode  :character   1102805:   136  
 Mean   :2017-01-03 14:05:16   2 :73085                            1107302:   136  
 3rd Qu.:2017-04-07 08:34:30   3 :42944                            1101211:   135  
 Max.   :2017-06-29 21:45:00   4 :10909                            1101207:   134  
                               5 :  719                            1101209:   133  
                               8 : 6413                            1102803:   128  
                               9 :10860                            1101208:   122  
                                                                   (Other):115119  
 Violation.Code                         Violation.Description   Plate.Year   
 50     :109115   EXPIRED METER                    :109115    2017   :63866  
 54     :  6975   RESIDENTIAL PERMIT ONLY          :  6975    2018   :49413  
 17     :  4308   PARK/STAND/STOP - RUSH HOUR      :  4308    2016   :15441  
 7      :  4215   OVERTIME PARKING - STREET        :  4215    ????   : 9170  
 35     :  4136   VIOLATION OFFICIAL SIGN ON STREET:  4136    2019   : 6721  
 34     :  3980   NPAT - ON STREET                 :  3980    2015   :  638  
 2      :  3130   UNREGISTERED VEHICLE ON STREET   :  3130    2010   :  366  
 36     :  2957   OFFICIAL SIGN OFF ST             :  2957    2014   :  131  
 42     :  2797   UNREG VEH OFF STREET             :  2797    2011   :   51  
 (Other):  4318   (Other)                          :  4318    (Other):  134  
  Vehicle.Make    Plate.Month     Vehicle.Type   Vehicle.Color  
 TOYT   :22228   SEP    :12543   4D     :69658   Black  :32413  
 HOND   :19010   APR    :12238   SU     :42727   Gray   :31789  
 FORD   :13339   JUN    :12117   VN     :11616   White  :28508  
 CHEV   :10471   AUG    :11817   2D     : 8272   Silver :19275  
 NISS   : 9665   JUL    :11707   PU     : 5815   Blue   :13485  
 MERZ   : 6238   MAR    :11280   TK     : 4127   Red    : 7848  
 BMW    : 5471   MAY    :11275   SW     : 1804   Green  : 3525  
 HYUN   : 5332   OCT    :11139   CV     : 1032   Magenta: 3187  
 VOLK   : 4700   DEC    :11038   TX     :  356   Gold   : 2780  
 (Other):49477   (Other):40777   (Other):  524   (Other): 3121  
                    Remarks       Ticket.Date          Ticket.Hour    Ticket.Day 
 02 HR PARKING          :53008   Min.   :2016-07-01   Min.   : 5.00   Sun:    0  
 01 HR PARKING          :26223   1st Qu.:2016-09-30   1st Qu.:11.00   Mon:17916  
 12 HR PARKING-NO PERMIT: 9614   Median :2017-01-09   Median :13.00   Tue:27913  
 03 HR PARKING          : 4369   Mean   :2017-01-03   Mean   :13.29   Wed:28810  
 09 HR PARKING-NO PERMIT: 3524   3rd Qu.:2017-04-07   3rd Qu.:15.00   Thu:28857  
 15 HR PARKING-NO PERMIT: 2727   Max.   :2017-06-29   Max.   :22.00   Fri:29313  
 04 HR PARKING          : 2333                                        Sat:13122  
 15 HR PARKING          : 1167                                                   
 EXPIRED TAGS           : 1150                                                   
 (Other)                :41816                                                   
  Ticket.Month    Ticket.Year  
 May    :13754   Min.   :2016  
 Mar    :13222   1st Qu.:2016  
 Aug    :13184   Median :2017  
 Apr    :12698   Mean   :2017  
 Jan    :12284   3rd Qu.:2017  
 Jun    :12267   Max.   :2017  
 Dec    :12145                 
 Jul    :12132                 
 Sep    :11428                 
 (Other):32817                 

Data Dictionary

Column Name Description Type Action Taken
Ticket.Number Ticket Number Plain Text Deleted
Date.Time Date/Time Date & Time Convert to POSIXct
Parking.Division Parking Division Plain Text
Ticket.Location Ticket Location Plain Text
Ticket.Date Ticket Date Date Created from Date.Time
Ticket.Hour Ticket hour of day Factor Created from Date.Time
Ticket.Day Ticket day of week Factor Created from Date.Time
Ticket.Month Ticket month of year Factor Created from Date.Time
Ticket.Year Ticket year Factor Created from Date.Time
Meter Meter Plain Text
Violation.Code Violation Code Plain Text
Violation.Description Violation Description Plain Text
Plate.Year Plate Year Plain Text
Vehicle.Make Vehicle Make Plain Text
Plate.Month Plate Month Plain Text
Vehicle.Type Vehicle Type Plain Text
Vehicle.Color Vehicle Color Plain Text
Remarks Remarks Plain Text
Issuing.Agency Issuing Agency Plain Text Deleted

Geo-Coding Ticket Locations

# Load ticket locations previously geocoded
geocoded <- read.csv(infile_geo);
# Remove data that is way outside of the geographic bounds of Montgomery County
rlb <- (geocoded$lat>=latBox[1]) & (geocoded$lat<=latBox[2]);
ulb <- (geocoded$lon>=lonBox[2]) & (geocoded$lon<=lonBox[1]);
geocoded <- geocoded[rlb&ulb,];
rm(rlb, ulb); # Clean Up
head(geocoded);

VISUALIZATION

Vehicle Characteristics

tmp <- data.frame(table(df$Parking.Division)) %>% filter(Freq > 0);
p1 <- ggplot(data = df[df$Parking.Division %in% tmp$Var1,]) +
  geom_bar(mapping = aes(x = Parking.Division)) +
  ggtitle("Tickets issued by Division");
tmp <- data.frame(table(df$Parking.Division)) %>% filter(Freq > 0);
p2 <- ggplot(data = df[df$Parking.Division %in% tmp$Var1,]) +
  geom_bar(mapping = aes(x = Parking.Division)) +
  ggtitle("Tickets issued by Division");
multiplot(p1, p2, layout=matrix(c(1,2),nrow=1,byrow=TRUE)); # Display

rm(p1, p2, tmp);                                            # cleanup

Vehicle Characteristics

tmp <- data.frame(table(df$Vehicle.Make)) %>% filter(Freq > 2000);
p1 <- ggplot(data = df[df$Vehicle.Make %in% tmp$Var1,]) +
  geom_bar(mapping = aes(x = Vehicle.Make)) +
  ggtitle("Top Vehicle Makes");
tmp <- data.frame(table(df$Vehicle.Type)) #%>% filter(Freq > 50);
p2 <- ggplot(data = df[df$Vehicle.Type %in% tmp$Var1,]) +
  geom_bar(mapping = aes(x = Vehicle.Type)) +
  ggtitle("Top Vehicle Types");
tmp <- data.frame(table(df$Vehicle.Color)) #%>% filter(Freq > 50);
p3 <- ggplot(data = df[df$Vehicle.Color %in% tmp$Var1,]) +
  geom_bar(mapping = aes(x = Vehicle.Color)) +
  ggtitle("Top Vehicle  Color");
multiplot(p3, p2, p1, layout=matrix(c(1,2,3),nrow=3,byrow=TRUE)); # Display

rm(p1, p2, p3, tmp);                                              # cleanup

Temporal Analysis

## Plots using ggplot (Histograms)
p1 <- ggplot(data = df) +
  geom_bar(mapping = aes(x = Ticket.Month)) +
  ggtitle("Tickets issued by month");
p2 <- ggplot(data = df) +
  geom_bar(mapping = aes(x = Ticket.Hour)) +
  ggtitle("Tickets issued by hour");
p3 <- ggplot(data = df) +
  geom_bar(mapping = aes(x = Ticket.Day)) +
  ggtitle("Tickets issued by day");
multiplot(p3, p2, p1, layout=matrix(c(1,2,3,3),nrow=2,byrow=TRUE)); # Display
rm(p1, p2, p3);                                                     # cleanup

Interactive Time series

## Plots using using dygraph (interactive Time series plot)
tmp <- as.data.frame(table(df$Ticket.Date));
don <- xts(x=tmp$Freq, order.by=ymd(tmp$Var1)); # Create the xts format 
dygraph(don, main="Daily Tickets Issued (2016-2017)") %>%
  dyOptions(labelsUTC= TRUE, fillGraph=TRUE, fillAlpha=0.1, drawGrid = TRUE, colors="#D8AE5A") %>%
  dyLimit(max(tmp$Freq), label=paste0("MAX=",max(tmp$Freq)), labelLoc="left") %>%
  dyLimit(mean(tmp$Freq),label=paste0("AVG=",as.integer(mean(tmp$Freq))),labelLoc="left") %>%
  dyLimit(min(tmp$Freq), label=paste0("MIN=",min(tmp$Freq)), labelLoc="left") %>%
  dyRangeSelector() %>%
  dyCrosshair(direction = "both") %>%
  dyHighlight(highlightCircleSize=5, highlightSeriesBackgroundAlpha=0.2, hideOnMouseOut=FALSE)  %>%
  dyRoller(rollPeriod = 1);

rm(tmp, don); # Clean up

Geo-spatial Custer Analysis

leaflet(data=geocoded) %>% 
  addTiles() %>% 
  addRectangles(
    lng1=lonBox[1], lat1=latBox[1],
    lng2=lonBox[2], lat2=latBox[2],
    fillColor = "transparent") %>%
  addTopoJSON(topo,
    weight = 1,
    color  = "#444444",
    fill   = TRUE)                               %>%
  addProviderTiles(providers$Stamen.TonerLines,
    options = providerTileOptions(opacity=0.35)) %>%
  addProviderTiles(providers$Stamen.Toner)       %>%
  addCircleMarkers(~lon, ~lat,
    color          = ~getColor(freq),
    stroke         = TRUE,
    fillOpacity    = 0.25,
    popup          = ~as.character(addr));
rm(topo);  # Clean Up
LS0tDQp0aXRsZTogICJGaW5hbCBQcm9qZWN0IC0tIFRlYW0gMSINCmF1dGhvcjogIlJpY2ssIFBhbSwgRXJpYywgU3RldmUiDQpkYXRlOiAgICJOb3ZlbWJlciAzMCwgMjAxOCINCmFsd2F5c19hbGxvd19odG1sOiB5ZXMNCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KICBodG1sX2RvY3VtZW50OiBkZWZhdWx0DQotLS0NCiMgQU5BTFlTSVMgT0YgTUNET1QgUEFSS0lORyBUSUNLRVRTDQpPdXIgZGF0YXNldCBjb250YWlucyBhbGwgcGFya2luZyBjaXRhdGlvbnMgaXNzdWVkIGJ5IE1vbnRnb21lcnkgQ291bnR5IERlcGFydG1lbnQgb2YgVHJhbnNwb3J0YXRpb24gKE1DRE9UKSBlbmZvcmNlbWVudCBwZXJzb25uZWwgaW4gdGhlIE1vbnRnb21lcnkgQ291bnR5IFBhcmtpbmcgTG90IERpc3RyaWN0cyBhbmQgVHJhbnNwb3J0YXRpb24gTWFuYWdlbWVudCBEaXN0cmljdHMgb2YgQmV0aGVzZGEsIE1vbnRnb21lcnkgSGlsbHMsIFNpbHZlciBTcHJpbmcsIFdoZWF0b24sIE5vcnRoIEJldGhlc2RhLCBGcmllbmRzaGlwIEhlaWdodHMsIEdyZWF0ZXIgU2VuZWNhIFNjaWVuY2UgQ2VudGVyIGFuZCB0aGUgUmVzaWRlbnRpYWwgUGVybWl0IFBhcmtpbmcgYXJlYXMuIFRoaXMgaW5jbHVkZXMgYWxsIG9uLXN0cmVldCBtZXRlcmVkIHBhcmtpbmcsIHB1YmxpYyBzdXJmYWNlIGxvdHMgYW5kIHB1YmxpYyBnYXJhZ2VzLiBUaGUgcGFya2luZyBjaXRhdGlvbnMgbGlzdCB0aGUgZm9sbG93aW5nOiBkYXRlL3RpbWUsIGxvY2F0aW9uLCB2ZWhpY2xlIGluZm9ybWF0aW9uLCBhbmQgZGVzY3JpcHRpb24gb2YgdGhlIHZpb2xhdGlvbi4NCg0KPGI+RGF0YSBQcm92aWRlcjwvYj46IDxpPk1DRyBFU0IgU2VydmljZSwgTW9udGdvbWVyeSBDb3VudHksIE1EPC9pPg0KDQojIyBQYXJraW5nIENpdGF0aW9ucyBGb3JtDQpTb21lIGNpdGF0aW9ucyBhcmUgaXNzdWVkIHVzaW5nIGhhbmRoZWxkIGVsZWN0cm9uaWMgdGlja2V0IHdyaXRpbmcgZXF1aXBtZW50IGFuZCBhcmUgZG93bmxvYWRlZCB3aXRoaW4ganVzdCBhIGZldyBkYXlzIGFmdGVyIGJlaW5nIGlzc3VlZC4gSGFuZHdyaXR0ZW4gY2l0YXRpb25zIGNhbiB0YWtlIHNldmVyYWwgd2Vla3MgdG8gYmUgZW50ZXJlZCBpbnRvIHRoZSBwYXJraW5nIGNpdGF0aW9uIHN5c3RlbS4NCg0KIVtBY3R1YWwgUGFya2luZyBUaWNrZXRdKHRpY2tldC5wbmcpDQpJbnRlZ3JpdHkgb2YgcGFya2luZyBjaXRhdGlvbnMgY2FuIGJlIGNvbXByb21pc2VkIGJ5IHNpbXBsZSB0eXBvZ3JhcGhpY2FsIGVycm9ycywgbWlzaWRlbnRpZmllZCB2ZWhpY2xlIG9yIGNvbnRhaW5zIGEgd3JvbmcgbG9jYXRpb24uICBGb3IgdGhlIHB1cnBvc2VzIG9mIHRoaXMgYW5hbHlzaXMgd2Ugc2hhbGwgYXNzdW1lIGVhY2ggcmVjb3JkIGluIHRoZSBkYXRhc2V0IGlzIGZhY3R1YWwuDQoqKioqKioNCiMgRVhQTE9SQVRPUlkgQU5BTFlTSVMNClRoaXMgZXhwbG9yYXRvcnkgYW5hbHlzaXMgd2lsbCB1c2UgUiBhcyB0aGUgdG9vbCB0byB2aXN1YWxpemUgYW5kIHRyYW5zZm9ybSB0aGUgZGF0YSBpbiBhIHN5c3RlbWF0aWMgd2F5LiAgV2l0aCBhbnkgZGF0YXNldCB0aGVyZSB3aWxsIGFsd2F5cyBiZSBhIG5lZWQgdG8gaW52ZXN0aWdhdGUgdGhlIHF1YWxpdHkgdGhlIGRhdGEuIA0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KDQojIFN0YXJ0IHdpdGggY2xlYW4gZW52aXJvbm1lbnQNCnJtKGxpc3Q9bHMoKSk7DQoNCiMgTGlicmFyaWVzDQoNCmxpYnJhcnkoZHlncmFwaHMpOyAjIEludGVyZmFjZSB0byBKYXZhU2NyaXB0IGNoYXJ0aW5nIGxpYnJhcnkNCmxpYnJhcnkoeHRzKTsgICAgICAjIFRvIG1ha2UgdGhlIGNvbnZlcnRpb24gZGF0YS1mcmFtZS94dHMgZm9ybWF0DQpsaWJyYXJ5KHRpZHl2ZXJzZSk7IyBDb2xsZWN0aW9uIG9mIFIgcGFja2FnZXMgZGVzaWduZWQgZm9yIGRhdGEgc2NpZW5jZQ0KbGlicmFyeShsdWJyaWRhdGUpOyMgTWFrZXMgaXQgZWFzaWVyIHRvIHdvcmsgd2l0aCBkYXRlcyBhbmQgdGltZXMNCmxpYnJhcnkobGVhZmxldCk7ICAjIEludGVyZmFjZSB0byBKUyBsaWJyYXJ5IHRvIG1ha2UgaW50ZXJhY3RpdmUgd2ViIG1hcHMgDQpsaWJyYXJ5KHJnZGFsKTsgICAgIyBHZW9zcGF0aWFsIERhdGEgQWJzdHJhY3Rpb24gTGlicmFyeQ0KDQojIEdsb2JhbCBWYXJpYWJsZXMNCg0KaW5maWxlX3JhdyAgPC0gIi4vZGF0YS9ET1RfUGFya2luZ19UaWNrZXRzLmNzdiI7DQppbmZpbGVfZ2VvICA8LSAiLi9kYXRhL2dlb2NvZGVkLmNzdiI7DQppbmZpbGVfanNvbiA8LSAiLi9kYXRhL2pzb24vZ3pfMjAxMF91c18wNTBfMDBfMjBtLmpzb24iOw0KbGF0Qm94ICAgICAgPC0gYyggMzguOSwgIDM5LjQpOyAgIyB+TGF0aXR1ZGFsIGxpbWl0cyBvZiBNb250b2dvbWVyeSBDb3VudHkNCmxvbkJveCAgICAgIDwtIGMoLTc2LjksIC03Ny40KTsgICMgfkxvbmdpdHVkYWwgbGltaXRzIG9mIE1vbnRvZ29tZXJ5IENvdW50eQ0KYGBgDQoNCmBgYHtyIGluY2x1ZGU9RkFMU0V9DQojIERlZmluZSBGdW5jdGlvbnMNCg0KIyBNdWx0aXBsZSBwbG90IGZ1bmN0aW9uDQojIFNPVVJDRTogaHR0cDovL3d3dy5jb29rYm9vay1yLmNvbS9HcmFwaHMvTXVsdGlwbGVfZ3JhcGhzX29uX29uZV9wYWdlXyhnZ3Bsb3QyKS8NCiMNCiMgZ2dwbG90IG9iamVjdHMgY2FuIGJlIHBhc3NlZCBpbiAuLi4sIG9yIHRvIHBsb3RsaXN0IChhcyBhIGxpc3Qgb2YgZ2dwbG90IG9iamVjdHMpDQojIC0gY29sczogICBOdW1iZXIgb2YgY29sdW1ucyBpbiBsYXlvdXQNCiMgLSBsYXlvdXQ6IEEgbWF0cml4IHNwZWNpZnlpbmcgdGhlIGxheW91dC4gSWYgcHJlc2VudCwgJ2NvbHMnIGlzIGlnbm9yZWQuDQojDQojIElmIHRoZSBsYXlvdXQgaXMgc29tZXRoaW5nIGxpa2UgbWF0cml4KGMoMSwyLDMsMyksIG5yb3c9MiwgYnlyb3c9VFJVRSksDQojIHRoZW4gcGxvdCAxIHdpbGwgZ28gaW4gdGhlIHVwcGVyIGxlZnQsIDIgd2lsbCBnbyBpbiB0aGUgdXBwZXIgcmlnaHQsIGFuZA0KIyAzIHdpbGwgZ28gYWxsIHRoZSB3YXkgYWNyb3NzIHRoZSBib3R0b20uDQojDQptdWx0aXBsb3QgPC0gZnVuY3Rpb24oLi4uLCBwbG90bGlzdD1OVUxMLCBmaWxlLCBjb2xzPTEsIGxheW91dD1OVUxMKSB7DQogIGxpYnJhcnkoZ3JpZCkNCiAgDQogICMgTWFrZSBhIGxpc3QgZnJvbSB0aGUgLi4uIGFyZ3VtZW50cyBhbmQgcGxvdGxpc3QNCiAgcGxvdHMgPC0gYyhsaXN0KC4uLiksIHBsb3RsaXN0KQ0KICANCiAgbnVtUGxvdHMgPSBsZW5ndGgocGxvdHMpDQogIA0KICAjIElmIGxheW91dCBpcyBOVUxMLCB0aGVuIHVzZSAnY29scycgdG8gZGV0ZXJtaW5lIGxheW91dA0KICBpZiAoaXMubnVsbChsYXlvdXQpKSB7DQogICAgIyBNYWtlIHRoZSBwYW5lbA0KICAgICMgbmNvbDogTnVtYmVyIG9mIGNvbHVtbnMgb2YgcGxvdHMNCiAgICAjIG5yb3c6IE51bWJlciBvZiByb3dzIG5lZWRlZCwgY2FsY3VsYXRlZCBmcm9tICMgb2YgY29scw0KICAgIGxheW91dCA8LSBtYXRyaXgoc2VxKDEsIGNvbHMgKiBjZWlsaW5nKG51bVBsb3RzL2NvbHMpKSwNCiAgICAgICAgICAgICAgICAgICAgIG5jb2wgPSBjb2xzLCBucm93ID0gY2VpbGluZyhudW1QbG90cy9jb2xzKSkNCiAgfQ0KICANCiAgaWYgKG51bVBsb3RzPT0xKSB7DQogICAgcHJpbnQocGxvdHNbWzFdXSkNCiAgICANCiAgfSBlbHNlIHsNCiAgICAjIFNldCB1cCB0aGUgcGFnZQ0KICAgIGdyaWQubmV3cGFnZSgpDQogICAgcHVzaFZpZXdwb3J0KHZpZXdwb3J0KGxheW91dCA9IGdyaWQubGF5b3V0KG5yb3cobGF5b3V0KSwgbmNvbChsYXlvdXQpKSkpDQogICAgDQogICAgIyBNYWtlIGVhY2ggcGxvdCwgaW4gdGhlIGNvcnJlY3QgbG9jYXRpb24NCiAgICBmb3IgKGkgaW4gMTpudW1QbG90cykgew0KICAgICAgIyBHZXQgdGhlIGksaiBtYXRyaXggcG9zaXRpb25zIG9mIHRoZSByZWdpb25zIHRoYXQgY29udGFpbiB0aGlzIHN1YnBsb3QNCiAgICAgIG1hdGNoaWR4IDwtIGFzLmRhdGEuZnJhbWUod2hpY2gobGF5b3V0ID09IGksIGFyci5pbmQgPSBUUlVFKSkNCiAgICAgIA0KICAgICAgcHJpbnQocGxvdHNbW2ldXSwgdnAgPSB2aWV3cG9ydChsYXlvdXQucG9zLnJvdyA9IG1hdGNoaWR4JHJvdywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGF5b3V0LnBvcy5jb2wgPSBtYXRjaGlkeCRjb2wpKQ0KICAgIH0NCiAgfQ0KfQ0KDQojIENvbG9yIFNlbGVjdG9yIGZ1bmN0aW9uDQpnZXRDb2xvciA8LSBmdW5jdGlvbihwMSkgew0KICBzYXBwbHkocDEsIGZ1bmN0aW9uKGYpIHsNCiAgICBpZihmIDw9IDI1MDApICAgICAgeyAiZ3JlZW4iIH0gDQogICAgZWxzZSBpZihmIDw9IDUwMDApIHsgInllbGxvdyIgfSANCiAgICBlbHNlIGlmKGYgPD0gNzUwMCkgeyAib3JhbmdlIiB9IA0KICAgIGVsc2UgICAgICAgICAgICAgICB7ICJyZWQiIH0gDQogIH0pOw0KfQ0KYGBgDQojIyBEYXRhIENsZWFuaW5nDQpTaW5jZSB0aGUgZGF0YXNldCB3ZXJlIGJhc2VkIG9uIGhhbmQgd3JpdHRlbiB0aWNrZXRzIGlzc3VlZCBieSBsYXcgZW5mb3JjZW1lbnQgYW5kIHRoZW4gdHJhbnNjcmliZWQgaW50byBhIGRhdGFzZXQsIHRoZXJlIGFyZSBpbmNvbnNpc3RlbmNpZXMgaW4gdGhlIGVudHJpZXMgKGVzcGVjaWFsbHkgd2hlbiB0aGUgdmlvbGF0aW9uIGlzIG5vdCBvbmUgdGhlIHZpb2xhdGlvbnMgdGhhdCBpcyBwcmUtcG9wdWxhdGVkIG9uIHRoZSBwYXJraW5nIHRpY2tldCBmb3JtKSwgYW5kIG1pc3NpbmcgZGF0YS4gQXMgd2UgZ28gdGhyb3VnaCB0aGUgZGF0YSBjbGVhbmluZyBwcm9jZXNzIHdlIHdpbGwgZGV0ZXJtaW5lIGlmIHRoZSBkYXRhIG1lZXRzIG91ciBleHBlY3RhdGlvbnMgb3Igbm90LiANCmBgYHtyfQ0KIyMgTG9hZCBjc3YgZGF0YSBzZXQNCmRmLnJhdyA8LSByZWFkLmNzdihpbmZpbGVfcmF3KTsNCnN0cihkZi5yYXcpOw0KYGBgDQoNCkVsbWluYXRlZCB0aHJlZSB2YXJhYmxlczogTWV0ZXIsIElzc3VpbmcuQWdlbmN5LCBhbmQgVGlja2V0Lk51bWJlcg0KVG8gZG8gZGF0YSBjbGVhbmluZywgeW914oCZbGwgbmVlZCB0byBkZXBsb3kgYWxsIHRoZSB0b29scyBvZiBFREE6IHZpc3VhbGlzYXRpb24sIHRyYW5zZm9ybWF0aW9uLCBhbmQgbW9kZWxsaW5nLiAgDQoNCmBgYHtyfQ0KIyMgQ3JlYXRlIGEgbmV3IGRhdGFzZXQgd2l0aG91dCBtaXNzaW5nIGRhdGENCmRmIDwtIGRmLnJhdzsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBDb3B5IG9yaWdpbmFsIA0KIyBkZiRNZXRlciAgICAgICAgICAgPC0gTlVMTDsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgUmVtb3ZlIHZhcmlhYmxlDQpkZiRJc3N1aW5nLkFnZW5jeSAgPC0gTlVMTDsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgUmVtb3ZlIHZhcmlhYmxlDQpkZiRUaWNrZXQuTnVtYmVyICAgPC0gTlVMTDsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgUmVtb3ZlIHZhcmlhYmxlDQpkZiREYXRlLlRpbWUgICAgICAgPC0gbWR5X2htcyhkZiREYXRlLlRpbWUpOyAgICAgICAgICAgICAgICMgQ29udmVydCB0byB0cyANCmRmJFRpY2tldC5Mb2NhdGlvbiA8LSBhcy5jaGFyYWN0ZXIoZGYkVGlja2V0LkxvY2F0aW9uKTsgICAgIyBDb252ZXJ0IHRvIHN0cmluZw0KZGYkVGlja2V0LkRhdGUgICAgIDwtIGRhdGUoZGYkRGF0ZS5UaW1lKTsgICAgICAgICAgICAgICAgICAjIERlcml2ZSBEYXRlDQpkZiRUaWNrZXQuSG91ciAgICAgPC0gaG91cihkZiREYXRlLlRpbWUpOyAgICAgICAgICAgICAgICAgICMgRGVyaXZlIEhvdXIgDQpkZiRUaWNrZXQuRGF5ICAgICAgPC0gd2RheShkZiREYXRlLlRpbWUsIGxhYmVsPVQsIGFiYnI9VCk7ICMgRGVyaXZlIERheSBvZiB3ZWVrDQpkZiRUaWNrZXQuTW9udGggICAgPC0gbW9udGgoZGYkRGF0ZS5UaW1lLGxhYmVsPVQsIGFiYnI9VCk7ICMgRGVyaXZlIE1vbnRoDQpkZiRUaWNrZXQuWWVhciAgICAgPC0geWVhcihkZiREYXRlLlRpbWUpOyAgICAgICAgICAgICAgICAgICMgRGVyaXZlIFllYXINCmRmJFBhcmtpbmcuRGl2aXNpb248LSBmYWN0b3IoY2FzZV93aGVuKA0KICBpcy5uYShkZi5yYXckUGFya2luZy5EaXZpc2lvbikgfiAiPz8iLCANCiAgVFJVRSB+IGFzLmNoYXJhY3RlcihkZi5yYXckUGFya2luZy5EaXZpc2lvbikNCikpOw0KZGYkVmlvbGF0aW9uLkNvZGUgIDwtIGZhY3RvcihjYXNlX3doZW4oDQogIGlzLm5hKGRmLnJhdyRWaW9sYXRpb24uQ29kZSkgfiBhcy5pbnRlZ2VyKDApLA0KICBUUlVFIH4gYXMuaW50ZWdlcihkZi5yYXckVmlvbGF0aW9uLkNvZGUpDQopKTsNCmRmJFZpb2xhdGlvbi5EZXNjcmlwdGlvbiAgPC0gZmFjdG9yKGNhc2Vfd2hlbigNCiAgaXMubmEoZGYucmF3JFZpb2xhdGlvbi5EZXNjcmlwdGlvbikgICAgICAgICAgICAgICAgICAgICAgIH4gIk5vIGRlc2NyaXB0aW9uIiwNCiAgZGYucmF3JFZpb2xhdGlvbi5EZXNjcmlwdGlvbj09IlVOUkVHIFZFSCBPRkYgU1RSRUVUIiAgICAgIH4gIlVOUkVHIFZFSCBPRkYgU1RSRUVUIiwgICAgICAgICAgICMgMDINCiAgZGYucmF3JFZpb2xhdGlvbi5EZXNjcmlwdGlvbj09IlBSSy9TVE5EL1NUT1AgUlVTSCBIIiAgICAgIH4gIlBBUksvU1RBTkQvU1RPUCAtIFJVU0ggSE9VUiIsICAgICMgMTcNCiAgZGYucmF3JFZpb2xhdGlvbi5EZXNjcmlwdGlvbj09IldJVEhJTiAxNSBGVCBIWURSQU5UIiAgICAgIH4gIldJVEhJTiAxNSBGRUVUIE9GIEZJUkUgSFlEUkFOVCIsICMgMTkNCiAgZGYucmF3JFZpb2xhdGlvbi5EZXNjcmlwdGlvbj09Ik5PIFNUTkQvUFJLIEZJUkUgTE4iICAgICAgIH4gIk5PIFNUTkQvUFJLIEZJUkUgTE4iLCAgICAgICAgICAgICMgMjUNCiAgZGYucmF3JFZpb2xhdGlvbi5EZXNjcmlwdGlvbj09Ik5QQVQgLSBPTiBTVFJFRVQiICAgICAgICAgIH4gIk5QQVQgLSBPTiBTVFJFRVQiLCAgICAgICAgICAgICAgICMgMzQNCiAgZGYucmF3JFZpb2xhdGlvbi5EZXNjcmlwdGlvbj09Ik9GRklDSUFMIFNJR04gT0ZGIFNUIiAgICAgIH4gIk9GRklDSUFMIFNJR04gT0ZGIFNUIiwgICAgICAgICAgICMgMzYNCiAgZGYucmF3JFZpb2xhdGlvbi5EZXNjcmlwdGlvbj09IkhBTkRJQ0FQIFBLRyBBUkVBIiAgICAgICAgIH4gIkhBTkRJQ0FQIEFSRUEiLCAgICAgICAgICAgICAgICAgICMgNDMNCiAgZGYucmF3JFZpb2xhdGlvbi5EZXNjcmlwdGlvbj09IkVYUElSRUQgUFJLRyBNRVRFUiIgICAgICAgIH4gIkVYUElSRUQgTUVURVIiLCAgICAgICAgICAgICAgICAgICMgNTANCiAgZGYucmF3JFZpb2xhdGlvbi5EZXNjcmlwdGlvbj09IlJFUyBQUktHIFBFUk1JVCBPTkxZIiAgICAgIH4gIlJFU0lERU5USUFMIFBFUk1JVCBPTkxZIiwgICAgICAgICMgNDMNCiAgZGYucmF3JFZpb2xhdGlvbi5EZXNjcmlwdGlvbj09Ik5UIElOIFBSSyBTUEMgT0ZGIFNUIiAgICAgIH4gIk5PVCBXSVRISU4gU1BBQ0UgLSBPRkYgU1RSRUVUIiwgICMgMzgNCiAgZGYucmF3JFZpb2xhdGlvbi5EZXNjcmlwdGlvbj09IkNPTU0uIFZFSC9CVVMvUkVDIElOIFJFU0lELiBaT05FIiB+ICJDT01NLiBWRUgvQlVTL1JFQyBJTiBSRVNJRC4gWk9ORSIsICAgICAgIyAwNA0KICBkZi5yYXckVmlvbGF0aW9uLkRlc2NyaXB0aW9uPT0iUEFSS0VEIFdJVEhJTiAzNSBGRUVUIE9GIElOVEVSU0VDVElPTiIgfiAiV0lUSElOIDM1IEZFRVQgT0YgSU5URVJTRUNUSU9OIiwgICAjIDA2DQogIGRmLnJhdyRWaW9sYXRpb24uRGVzY3JpcHRpb249PSJXVEhJTiA1IEZUIERSSVZFV0FZIiAgICAgICB+ICJXSVRISU4gNSBGRUVUIE9GIERSSVZFV0FZIC0gT04gU1RSRUVUIiwjIDA4DQogIGRmLnJhdyRWaW9sYXRpb24uRGVzY3JpcHRpb249PSJPRkZJQ0lBTCBTSUdOIE9GRiBTVCIgICAgICB+ICJPRkZJQ0lBTCBTSUdOIE9GRiBTVFJFRVQiLCAgICAgICAjID8/DQogIGRmLnJhdyRWaW9sYXRpb24uRGVzY3JpcHRpb249PSJPVlIgMjQgSFJTIE9GRiBTVFJFRSIgICAgICB+ICJPVkVSIDI0IEhSUyBPRkYgU1RSRUVUIiwgICAgICAgICAjID8/DQogIGRmLnJhdyRWaW9sYXRpb24uRGVzY3JpcHRpb249PSJVTlJFR0lTVEVSRUQgVkVIICBPTiBTVFIiICB+ICJVTlJFR0lTVEVSRUQgVkVISUNMRSBPTiBTVFJFRVQiLCAjID8/DQogIGRmLnJhdyRWaW9sYXRpb24uRGVzY3JpcHRpb249PSJXSVRISU4gMjAgRlQgQ1JPU1NXSyIgICAgICB+ICJXSVRISU4gMjAgRlQgQ1JPU1NXQUxLIiwgICAgICAgICAjID8/DQogIGRmLnJhdyRWaW9sYXRpb24uRGVzY3JpcHRpb249PSJPVkVSVE0gUFJLRyAtIFNUUkVFVCIgICAgICB+ICJPVkVSVElNRSBQQVJLSU5HIC0gU1RSRUVUIiwgICAgICAjID8/DQogIGRmLnJhdyRWaW9sYXRpb24uRGVzY3JpcHRpb249PSJVTlJFRyBWRUggT0ZGIFNUUkVFVCIgICAgICB+ICJVTlJFR0lTVEVSRUQgVkVISUNMRSBPRkYgU1RSRUVUIiwjID8/DQogIGRmLnJhdyRWaW9sYXRpb24uRGVzY3JpcHRpb249PSJOTyBTVEFORElORyAtIE9OIFNUUiIgICAgICB+ICJOTyBTVEFORElORyAtIE9OIFNUUkVFVCIsICAgICAgICAjID8/DQogIGRmLnJhdyRWaW9sYXRpb24uRGVzY3JpcHRpb249PSJET1VCTEUgUEFSS0VEIC0gU1RSRSIgICAgICB+ICJET1VCTEUgUEFSS0VEIC0gU1RSRUVUIiwgICAgICAgICAjID8/DQogIGRmLnJhdyRWaW9sYXRpb24uRGVzY3JpcHRpb249PSJJTVBFRElORyBUUkFGRi9TQUZFVCIgICAgICB+ICJJTVBFRElORyBUUkFGRklDL1NBRkVUWSIsICAgICAgICAjID8/DQogIGRmLnJhdyRWaW9sYXRpb24uRGVzY3JpcHRpb249PSJQQVJLIE1PUkUgMSBGVCBDVVJCIiAgICAgICB+ICJQQVJLIE1PUkUgVEhBTiAxIEZFRVQgRlJPTSBDVVJCIiwjID8/DQogIGRmLnJhdyRWaW9sYXRpb24uRGVzY3JpcHRpb249PSJWSU9MQVRJT04gT0ZGSUNJQUwgU0lHTiAgT04gU1RSRUVUIiB+ICJWSU9MQVRJT04gT0ZGSUNJQUwgU0lHTiBPTiBTVFJFRVQiLCAgICAjID8/ICAgIA0KICBUUlVFIH4gYXMuY2hhcmFjdGVyKGRmLnJhdyRWaW9sYXRpb24uQ29kZSkNCikpOw0KZGYkUGxhdGUuTW9udGggICAgIDwtIGZhY3RvcihjYXNlX3doZW4oDQogIGRmLnJhdyRQbGF0ZS5Nb250aD09MSAgfiAiSkFOIiwgZGYucmF3JFBsYXRlLk1vbnRoPT0yICB+ICJGRUIiLA0KICBkZi5yYXckUGxhdGUuTW9udGg9PTMgIH4gIk1BUiIsIGRmLnJhdyRQbGF0ZS5Nb250aD09NCAgfiAiQVBSIiwNCiAgZGYucmF3JFBsYXRlLk1vbnRoPT01ICB+ICJNQVkiLCBkZi5yYXckUGxhdGUuTW9udGg9PTYgIH4gIkpVTiIsDQogIGRmLnJhdyRQbGF0ZS5Nb250aD09NyAgfiAiSlVMIiwgZGYucmF3JFBsYXRlLk1vbnRoPT04ICB+ICJBVUciLA0KICBkZi5yYXckUGxhdGUuTW9udGg9PTkgIH4gIlNFUCIsIGRmLnJhdyRQbGF0ZS5Nb250aD09MTAgfiAiT0NUIiwNCiAgZGYucmF3JFBsYXRlLk1vbnRoPT0xMSB+ICJOT1YiLCBkZi5yYXckUGxhdGUuTW9udGg9PTEyIH4gIkRFQyIsDQogIFRSVUUgfiAiPz8/Ig0KKSk7DQpkZiRQbGF0ZS5ZZWFyICAgICA8LSBmYWN0b3IoY2FzZV93aGVuKA0KICBkZi5yYXckUGxhdGUuWWVhcj09IjAwIiAgfCBkZi5yYXckUGxhdGUuWWVhcj09IiIgfCBkZi5yYXckUGxhdGUuWWVhcj09IjM1NDAiIHwNCiAgZGYucmF3JFBsYXRlLlllYXI9PSJOQUEiIHwgaXMubmEoZGYucmF3JFBsYXRlLlllYXIpICB+ICI/Pz8/IiwNCiAgZGYucmF3JFBsYXRlLlllYXI9PSIyMDciICB+ICIyMDA3IiwNCiAgZGYucmF3JFBsYXRlLlllYXI9PSI5IiAgICB+ICIyMDA5IiwNCiAgZGYucmF3JFBsYXRlLlllYXI9PSIyMDIwIiB+ICIyMDEwIiwNCiAgZGYucmF3JFBsYXRlLlllYXI9PSIyMDIxIiB+ICIyMDExIiwNCiAgZGYucmF3JFBsYXRlLlllYXI9PSIyMDIyIiB+ICIyMDEyIiwNCiAgZGYucmF3JFBsYXRlLlllYXI9PSIyMDIzIiB+ICIyMDEzIiwNCiAgZGYucmF3JFBsYXRlLlllYXI9PSIyMDI0IiB+ICIyMDE0IiwNCiAgZGYucmF3JFBsYXRlLlllYXI9PSIwMjE2IiB+ICIyMDE2IiwNCiAgZGYucmF3JFBsYXRlLlllYXI9PSIxNyIgICB8IGRmLnJhdyRQbGF0ZS5ZZWFyPT0iMDIxNyIgfCBkZi5yYXckUGxhdGUuWWVhcj09IjEwMTciIH4gIjIwMTciLA0KICBkZi5yYXckUGxhdGUuWWVhcj09IjEwMTgiIHwgZGYucmF3JFBsYXRlLlllYXI9PSIyMTE4IiB8IGRmLnJhdyRQbGF0ZS5ZZWFyPT0iMjAyOCIgfiAiMjAxOCIsDQogIGRmLnJhdyRQbGF0ZS5ZZWFyPT0iNTAxOSIgfCBkZi5yYXckUGxhdGUuWWVhcj09IjAyMTkiIH4gIjIwMTkiLA0KICBUUlVFIH4gYXMuY2hhcmFjdGVyKGRmLnJhdyRQbGF0ZS5ZZWFyKQ0KKSk7DQpkZiRSZW1hcmtzICAgICAgICA8LSBhcy5mYWN0b3IoY2FzZV93aGVuKA0KICBkZi5yYXckUmVtYXJrcz09IjEgSFIgUEFSS0lORy4iICAgICAgICAgICAgIH4gIjAxIEhSIFBBUktJTkciLA0KICBkZi5yYXckUmVtYXJrcz09IjEgSFIgUEFSS0lORyIgICAgICAgICAgICAgIH4gIjAxIEhSIFBBUktJTkciLA0KICBkZi5yYXckUmVtYXJrcz09IjIgSFIgUEFSS0lORyIgICAgICAgICAgICAgIH4gIjAyIEhSIFBBUktJTkciLA0KICBkZi5yYXckUmVtYXJrcz09IjMgSFIgUEFSS0lORyIgICAgICAgICAgICAgIH4gIjAzIEhSIFBBUktJTkciLA0KICBkZi5yYXckUmVtYXJrcz09IjQgSFIgUEFSS0lORyIgICAgICAgICAgICAgIH4gIjA0IEhSIFBBUktJTkciLA0KICBkZi5yYXckUmVtYXJrcz09IjkgSFIgUEFSS0lORyIgICAgICAgICAgICAgIH4gIjA5IEhSIFBBUktJTkciLA0KICBkZi5yYXckUmVtYXJrcz09IjkgSFIgUEFSS0lORyBOTyBQRVJNSVQiICAgIH4gIjA5IEhSIFBBUktJTkctTk8gUEVSTUlUIiwNCiAgZGYucmF3JFJlbWFya3M9PSI5IEhSIFBBUktJTkcgLSBOTyBQRVJNSVQiICB+ICIwOSBIUiBQQVJLSU5HLU5PIFBFUk1JVCIsDQogIGRmLnJhdyRSZW1hcmtzPT0iOSBIUiBQQVJLSU5HL05PIFBFUk1JVCIgICAgfiAiMDkgSFIgUEFSS0lORy1OTyBQRVJNSVQiLA0KICBkZi5yYXckUmVtYXJrcz09IjkgSFIgUEFSS0lORy9OTyBQRVJJVCIgICAgIH4gIjA5IEhSIFBBUktJTkctTk8gUEVSTUlUIiwNCiAgZGYucmF3JFJlbWFya3M9PSI5IEhSIFBBUktJTkcvTk8gVklTSUJMRSBQRVJNSVQiICAgfiAiMDkgSFIgUEFSS0lORy1OTyBQRVJNSVQiLA0KICBkZi5yYXckUmVtYXJrcz09IjkgSFIgUEFSS0lORyBOTyBQRVJNSVQgSU5TSURFIiAgICB+ICIwOSBIUiBQQVJLSU5HLU5PIFBFUk1JVCIsDQogIGRmLnJhdyRSZW1hcmtzPT0iOSBIUiBQQVJLSU5HLU5PIFBFUk1JVCBESVNQTEFZRUQiIH4gIjA5IEhSIFBBUktJTkctTk8gUEVSTUlUIiwNCiAgZGYucmF3JFJlbWFya3M9PSIxMEhSIFBBUktJTkcgTk8gUEVSTUlUIiAgICB+ICIxMCBIUiBQQVJLSU5HLU5PIFBFUk1JVCIsDQogIGRmLnJhdyRSZW1hcmtzPT0iMTIgSFIgUEFSS0lORy9OTyBQRVJNSVQiICAgfiAiMTIgSFIgUEFSS0lORy1OTyBQRVJNSVQiLA0KICBkZi5yYXckUmVtYXJrcz09IjEyIEhSIFBBUktJTkcgIE5PIFBFUk1JVCIgIH4gIjEyIEhSIFBBUktJTkctTk8gUEVSTUlUIiwNCiAgZGYucmF3JFJlbWFya3M9PSIxMiBIUiBQQVJLSU5HIE5PIFBFUk1JVCIgICB+ICIxMiBIUiBQQVJLSU5HLU5PIFBFUk1JVCIsDQogIGRmLnJhdyRSZW1hcmtzPT0iMTIgSFIgUEFSS0lORyAtIE5PIFBFUk1JVCIgfiAiMTIgSFIgUEFSS0lORy1OTyBQRVJNSVQiLA0KICBkZi5yYXckUmVtYXJrcz09IjEyIEhSIFBBUktJTkcgTk8gUEVSTUlUIElOU0lERSIgIH4gIjEyIEhSIFBBUktJTkctTk8gUEVSTUlUIiwNCiAgZGYucmF3JFJlbWFya3M9PSIxNSBIUiBQQVJLSU5HIE5PICBQRVJNSVQiICB+ICIxNSBIUiBQQVJLSU5HLU5PIFBFUk1JVCIsDQogIGRmLnJhdyRSZW1hcmtzPT0iMTUgSFIgUEFSS0lORyBOTyBQRVJNSVQiICAgfiAiMTUgSFIgUEFSS0lORy1OTyBQRVJNSVQiLA0KICBkZi5yYXckUmVtYXJrcz09IjE1IEhSIFBBUktJTkcvTk8gUEVSTUlUIiAgIH4gIjE1IEhSIFBBUktJTkctTk8gUEVSTUlUIiwNCiAgVFJVRSB+IGFzLmNoYXJhY3RlcihkZi5yYXckUmVtYXJrcykNCikpOw0KZGYkVmVoaWNsZS5Db2xvciA8LSBhcy5mYWN0b3IoY2FzZV93aGVuKA0KICBkZi5yYXckVmVoaWNsZS5Db2xvcj09IkFMIiAgfiAiQWx1bWludW0iLA0KICBkZi5yYXckVmVoaWNsZS5Db2xvcj09IkFNIiAgfiAiQW1iZXIiLA0KICBkZi5yYXckVmVoaWNsZS5Db2xvcj09IkJHIiAgfiAiQmVpZ2UiLA0KICBkZi5yYXckVmVoaWNsZS5Db2xvcj09IkJLIiAgfiAiQmxhY2siLA0KICBkZi5yYXckVmVoaWNsZS5Db2xvcj09IkJMIiB8IGRmLnJhdyRWZWhpY2xlLkNvbG9yPT0iQlUiICB+ICJCbHVlIiwNCiAgZGYucmF3JFZlaGljbGUuQ29sb3I9PSJCTiIgfCBkZi5yYXckVmVoaWNsZS5Db2xvcj09IkJSIiAgfiAiQnJvd24iLA0KICBkZi5yYXckVmVoaWNsZS5Db2xvcj09IkJaIiAgfiAiQnJvbnplIiwNCiAgZGYucmF3JFZlaGljbGUuQ29sb3I9PSJDSCIgIH4gIkNoYXJjb2FsIiwNCiAgZGYucmF3JFZlaGljbGUuQ29sb3I9PSJDTCIgIH4gIkNsZWFyIiwNCiAgZGYucmF3JFZlaGljbGUuQ29sb3I9PSJESyIgIH4gIkRhcmsiLA0KICBkZi5yYXckVmVoaWNsZS5Db2xvcj09IkdEIiB8IGRmLnJhdyRWZWhpY2xlLkNvbG9yPT0iR08iICB+ICJHb2xkIiwNCiAgZGYucmF3JFZlaGljbGUuQ29sb3I9PSJHTiIgfCBkZi5yYXckVmVoaWNsZS5Db2xvcj09IkdSIiAgfiAiR3JlZW4iLA0KICBkZi5yYXckVmVoaWNsZS5Db2xvcj09IkdZIiAgfiAiR3JheSIsDQogIGRmLnJhdyRWZWhpY2xlLkNvbG9yPT0iR1QiICB+ICJHcmFuaXRlIiwNCiAgZGYucmF3JFZlaGljbGUuQ29sb3I9PSJJViIgIH4gIkl2b3J5IiwNCiAgZGYucmF3JFZlaGljbGUuQ29sb3I9PSJMVCIgIH4gIkxpZ2h0IiwNCiAgZGYucmF3JFZlaGljbGUuQ29sb3I9PSJNQSIgIH4gIk1hZ2VudGEiLA0KICBkZi5yYXckVmVoaWNsZS5Db2xvcj09Ik1DIiAgfiAiTUMiLA0KICBkZi5yYXckVmVoaWNsZS5Db2xvcj09Ik9MIiAgfiAiT2xpdmUiLA0KICBkZi5yYXckVmVoaWNsZS5Db2xvcj09Ik9QIiAgfiAiT3BhcXVlIiwNCiAgZGYucmF3JFZlaGljbGUuQ29sb3I9PSJPUiIgfCBkZi5yYXckVmVoaWNsZS5Db2xvcj09Ik9SIiAgfiAiT3JhbmdlIiwNCiAgZGYucmF3JFZlaGljbGUuQ29sb3I9PSJQSSIgfCBkZi5yYXckVmVoaWNsZS5Db2xvcj09IlBLIiAgfiAiUGluayIsDQogIGRmLnJhdyRWZWhpY2xlLkNvbG9yPT0iUFUiICB+ICJQdXJwbGUiLA0KICBkZi5yYXckVmVoaWNsZS5Db2xvcj09IlJEIiB8IGRmLnJhdyRWZWhpY2xlLkNvbG9yPT0iUkUiICB+ICJSZWQiLA0KICBkZi5yYXckVmVoaWNsZS5Db2xvcj09IlNJIiAgfiAiU2lsdmVyIiwNCiAgZGYucmF3JFZlaGljbGUuQ29sb3I9PSJTTSIgIH4gIlNtb2tlIiwNCiAgZGYucmF3JFZlaGljbGUuQ29sb3I9PSJUTiIgIH4gIlRhbiIsDQogIGRmLnJhdyRWZWhpY2xlLkNvbG9yPT0iVEsiIHwgZGYucmF3JFZlaGljbGUuQ29sb3I9PSJUUSIgIH4gIlR1cnF1b2lzZSIsDQogIGRmLnJhdyRWZWhpY2xlLkNvbG9yPT0iVlQiICB+ICJWaW9sZXQiLA0KICBkZi5yYXckVmVoaWNsZS5Db2xvcj09IldIIiB8IGRmLnJhdyRWZWhpY2xlLkNvbG9yPT0iV1QiICB+ICJXaGl0ZSIsDQogIGRmLnJhdyRWZWhpY2xlLkNvbG9yPT0iWUUiIHwgZGYucmF3JFZlaGljbGUuQ29sb3I9PSJZTCIgIH4gIlllbGxvdyIsDQogIFRSVUUgfiBhcy5jaGFyYWN0ZXIoZGYucmF3JFZlaGljbGUuQ29sb3IpDQopKTsNCmRmJE1ldGVyIDwtIGFzLmZhY3RvcihjYXNlX3doZW4oDQogIGlzLm5hKGRmLnJhdyRNZXRlcikgICB+IGFzLmNoYXJhY3RlcigiPz8/IiksDQogIGRmLnJhdyRNZXRlcj09Ik4vQSIgICB+IGFzLmNoYXJhY3RlcigiPz8/IiksDQogIGRmLnJhdyRNZXRlcj09Il4oKSEiICB+IGFzLmNoYXJhY3RlcigiPz8/IiksDQogIFRSVUUgfiBhcy5jaGFyYWN0ZXIoZGYucmF3JE1ldGVyKQ0KKSk7DQpkZiA8LSBuYS5vbWl0KGRmKTsNCnN1bW1hcnkoZGYsIG1heHN1bT0xMCk7DQpgYGANCiMjIERhdGEgRGljdGlvbmFyeQ0KDQp8Q29sdW1uIE5hbWUgICAgICAgICAgfERlc2NyaXB0aW9uICAgICAgICAgIHxUeXBlICAgICAgIHwgQWN0aW9uIFRha2VuDQp8Oi0tLS0tLS0tLS0tLS0tLS0tLS0tfDotLS0tLS0tLS0tLS0tLS0tLS0tLXw6LS0tLS0tLS0tLXw6LS0tLS0tLS0tLS0tDQp8VGlja2V0Lk51bWJlciAgICAgICAgfFRpY2tldCBOdW1iZXIgICAgICAgIHxQbGFpbiBUZXh0IHw8Yj5EZWxldGVkPC9iPg0KfERhdGUuVGltZSAgICAgICAgICAgIHxEYXRlL1RpbWUgICAgICAgICAgICB8RGF0ZSAmIFRpbWV8Q29udmVydCB0byBQT1NJWGN0DQp8UGFya2luZy5EaXZpc2lvbiAgICAgfFBhcmtpbmcgRGl2aXNpb24gICAgIHxQbGFpbiBUZXh0IHwNCnxUaWNrZXQuTG9jYXRpb24gICAgICB8VGlja2V0IExvY2F0aW9uICAgICAgfFBsYWluIFRleHQgfA0KfDxpPlRpY2tldC5EYXRlPC9pPiAgIHxUaWNrZXQgRGF0ZSAgICAgICAgICB8RGF0ZSAgICAgICB8PGk+Q3JlYXRlZCBmcm9tIERhdGUuVGltZTwvaT4NCnw8aT5UaWNrZXQuSG91cjwvaT4gICB8VGlja2V0IGhvdXIgb2YgZGF5ICAgfEZhY3RvciAgICAgfDxpPkNyZWF0ZWQgZnJvbSBEYXRlLlRpbWU8L2k+DQp8PGk+VGlja2V0LkRheTwvaT4gICAgfFRpY2tldCBkYXkgb2Ygd2VlayAgIHxGYWN0b3IgICAgIHw8aT5DcmVhdGVkIGZyb20gRGF0ZS5UaW1lPC9pPg0KfDxpPlRpY2tldC5Nb250aDwvaT4gIHxUaWNrZXQgbW9udGggb2YgeWVhciB8RmFjdG9yICAgICB8PGk+Q3JlYXRlZCBmcm9tIERhdGUuVGltZTwvaT4NCnw8aT5UaWNrZXQuWWVhcjwvaT4gICB8VGlja2V0IHllYXIgICAgICAgICAgfEZhY3RvciAgICAgfDxpPkNyZWF0ZWQgZnJvbSBEYXRlLlRpbWU8L2k+DQp8TWV0ZXIgICAgICAgICAgICAgICAgfE1ldGVyICAgICAgICAgICAgICAgIHxQbGFpbiBUZXh0IHwNCnxWaW9sYXRpb24uQ29kZSAgICAgICB8VmlvbGF0aW9uIENvZGUgICAgICAgfFBsYWluIFRleHQgfA0KfFZpb2xhdGlvbi5EZXNjcmlwdGlvbnxWaW9sYXRpb24gRGVzY3JpcHRpb258UGxhaW4gVGV4dCB8DQp8UGxhdGUuWWVhciAgICAgICAgICAgfFBsYXRlIFllYXIgICAgICAgICAgIHxQbGFpbiBUZXh0IHwNCnxWZWhpY2xlLk1ha2UgICAgICAgICB8VmVoaWNsZSBNYWtlICAgICAgICAgfFBsYWluIFRleHQgfA0KfFBsYXRlLk1vbnRoICAgICAgICAgIHxQbGF0ZSBNb250aCAgICAgICAgICB8UGxhaW4gVGV4dCB8DQp8VmVoaWNsZS5UeXBlICAgICAgICAgfFZlaGljbGUgVHlwZSAgICAgICAgIHxQbGFpbiBUZXh0IHwNCnxWZWhpY2xlLkNvbG9yICAgICAgICB8VmVoaWNsZSBDb2xvciAgICAgICAgfFBsYWluIFRleHQgfA0KfFJlbWFya3MgICAgICAgICAgICAgIHxSZW1hcmtzICAgICAgICAgICAgICB8UGxhaW4gVGV4dCB8DQp8SXNzdWluZy5BZ2VuY3kgICAgICAgfElzc3VpbmcgQWdlbmN5ICAgICAgIHxQbGFpbiBUZXh0IHw8Yj5EZWxldGVkPC9iPg0KDQoNCg0KIyMgR2VvLUNvZGluZyBUaWNrZXQgTG9jYXRpb25zDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBMb2FkIHRpY2tldCBsb2NhdGlvbnMgcHJldmlvdXNseSBnZW9jb2RlZA0KZ2VvY29kZWQgPC0gcmVhZC5jc3YoaW5maWxlX2dlbyk7DQoNCiMgUmVtb3ZlIGRhdGEgdGhhdCBpcyB3YXkgb3V0c2lkZSBvZiB0aGUgZ2VvZ3JhcGhpYyBib3VuZHMgb2YgTW9udGdvbWVyeSBDb3VudHkNCnJsYiA8LSAoZ2VvY29kZWQkbGF0Pj1sYXRCb3hbMV0pICYgKGdlb2NvZGVkJGxhdDw9bGF0Qm94WzJdKTsNCnVsYiA8LSAoZ2VvY29kZWQkbG9uPj1sb25Cb3hbMl0pICYgKGdlb2NvZGVkJGxvbjw9bG9uQm94WzFdKTsNCmdlb2NvZGVkIDwtIGdlb2NvZGVkW3JsYiZ1bGIsXTsNCnJtKHJsYiwgdWxiKTsgIyBDbGVhbiBVcA0KaGVhZChnZW9jb2RlZCk7DQpgYGANCiMgVklTVUFMSVpBVElPTg0KIyMgVmVoaWNsZSBDaGFyYWN0ZXJpc3RpY3MNCmBgYHtyIGVjaG89VFJVRSwgZmlnLmhlaWdodD04LjUsIGZpZy53aWR0aD0xMSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgb3V0LndpZHRoPSIxMDAlIn0NCnRtcCA8LSBkYXRhLmZyYW1lKHRhYmxlKGRmJFBhcmtpbmcuRGl2aXNpb24pKSAlPiUgZmlsdGVyKEZyZXEgPiAwKTsNCnAxIDwtIGdncGxvdChkYXRhID0gZGZbZGYkUGFya2luZy5EaXZpc2lvbiAlaW4lIHRtcCRWYXIxLF0pICsNCiAgZ2VvbV9iYXIobWFwcGluZyA9IGFlcyh4ID0gUGFya2luZy5EaXZpc2lvbikpICsNCiAgZ2d0aXRsZSgiVGlja2V0cyBpc3N1ZWQgYnkgRGl2aXNpb24iKTsNCnRtcCA8LSBkYXRhLmZyYW1lKHRhYmxlKGRmJFBhcmtpbmcuRGl2aXNpb24pKSAlPiUgZmlsdGVyKEZyZXEgPiAwKTsNCnAyIDwtIGdncGxvdChkYXRhID0gZGZbZGYkUGFya2luZy5EaXZpc2lvbiAlaW4lIHRtcCRWYXIxLF0pICsNCiAgZ2VvbV9iYXIobWFwcGluZyA9IGFlcyh4ID0gUGFya2luZy5EaXZpc2lvbikpICsNCiAgZ2d0aXRsZSgiVGlja2V0cyBpc3N1ZWQgYnkgRGl2aXNpb24iKTsNCm11bHRpcGxvdChwMSwgcDIsIGxheW91dD1tYXRyaXgoYygxLDIpLG5yb3c9MSxieXJvdz1UUlVFKSk7ICMgRGlzcGxheQ0Kcm0ocDEsIHAyLCB0bXApOyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBjbGVhbnVwDQpgYGANCiMjIFZlaGljbGUgQ2hhcmFjdGVyaXN0aWNzDQpgYGB7ciBlY2hvPVRSVUUsIGZpZy5oZWlnaHQ9OC41LCBmaWcud2lkdGg9MTEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIG91dC53aWR0aD0iMTAwJSJ9DQp0bXAgPC0gZGF0YS5mcmFtZSh0YWJsZShkZiRWZWhpY2xlLk1ha2UpKSAlPiUgZmlsdGVyKEZyZXEgPiAyMDAwKTsNCnAxIDwtIGdncGxvdChkYXRhID0gZGZbZGYkVmVoaWNsZS5NYWtlICVpbiUgdG1wJFZhcjEsXSkgKw0KICBnZW9tX2JhcihtYXBwaW5nID0gYWVzKHggPSBWZWhpY2xlLk1ha2UpKSArDQogIGdndGl0bGUoIlRvcCBWZWhpY2xlIE1ha2VzIik7DQp0bXAgPC0gZGF0YS5mcmFtZSh0YWJsZShkZiRWZWhpY2xlLlR5cGUpKSAjJT4lIGZpbHRlcihGcmVxID4gNTApOw0KcDIgPC0gZ2dwbG90KGRhdGEgPSBkZltkZiRWZWhpY2xlLlR5cGUgJWluJSB0bXAkVmFyMSxdKSArDQogIGdlb21fYmFyKG1hcHBpbmcgPSBhZXMoeCA9IFZlaGljbGUuVHlwZSkpICsNCiAgZ2d0aXRsZSgiVG9wIFZlaGljbGUgVHlwZXMiKTsNCnRtcCA8LSBkYXRhLmZyYW1lKHRhYmxlKGRmJFZlaGljbGUuQ29sb3IpKSAjJT4lIGZpbHRlcihGcmVxID4gNTApOw0KcDMgPC0gZ2dwbG90KGRhdGEgPSBkZltkZiRWZWhpY2xlLkNvbG9yICVpbiUgdG1wJFZhcjEsXSkgKw0KICBnZW9tX2JhcihtYXBwaW5nID0gYWVzKHggPSBWZWhpY2xlLkNvbG9yKSkgKw0KICBnZ3RpdGxlKCJUb3AgVmVoaWNsZSAgQ29sb3IiKTsNCm11bHRpcGxvdChwMywgcDIsIHAxLCBsYXlvdXQ9bWF0cml4KGMoMSwyLDMpLG5yb3c9MyxieXJvdz1UUlVFKSk7ICMgRGlzcGxheQ0Kcm0ocDEsIHAyLCBwMywgdG1wKTsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBjbGVhbnVwDQpgYGANCiMjIFRlbXBvcmFsIEFuYWx5c2lzDQpgYGB7ciBlY2hvPVRSVUUsIGZpZy5oZWlnaHQ9OC41LCBmaWcud2lkdGg9MTEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIG91dC53aWR0aD0iMTAwJSJ9DQojIyBQbG90cyB1c2luZyBnZ3Bsb3QgKEhpc3RvZ3JhbXMpDQpwMSA8LSBnZ3Bsb3QoZGF0YSA9IGRmKSArDQogIGdlb21fYmFyKG1hcHBpbmcgPSBhZXMoeCA9IFRpY2tldC5Nb250aCkpICsNCiAgZ2d0aXRsZSgiVGlja2V0cyBpc3N1ZWQgYnkgbW9udGgiKTsNCnAyIDwtIGdncGxvdChkYXRhID0gZGYpICsNCiAgZ2VvbV9iYXIobWFwcGluZyA9IGFlcyh4ID0gVGlja2V0LkhvdXIpKSArDQogIGdndGl0bGUoIlRpY2tldHMgaXNzdWVkIGJ5IGhvdXIiKTsNCnAzIDwtIGdncGxvdChkYXRhID0gZGYpICsNCiAgZ2VvbV9iYXIobWFwcGluZyA9IGFlcyh4ID0gVGlja2V0LkRheSkpICsNCiAgZ2d0aXRsZSgiVGlja2V0cyBpc3N1ZWQgYnkgZGF5Iik7DQptdWx0aXBsb3QocDMsIHAyLCBwMSwgbGF5b3V0PW1hdHJpeChjKDEsMiwzLDMpLG5yb3c9MixieXJvdz1UUlVFKSk7ICMgRGlzcGxheQ0Kcm0ocDEsIHAyLCBwMyk7ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGNsZWFudXANCmBgYA0KIyMgSW50ZXJhY3RpdmUgVGltZSBzZXJpZXMNCmBgYHtyIGVjaG89VFJVRSwgZmlnLmhlaWdodD04LjUsIGZpZy53aWR0aD0xMSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgb3V0LndpZHRoPSIxMDAlIn0NCiMjIFBsb3RzIHVzaW5nIHVzaW5nIGR5Z3JhcGggKGludGVyYWN0aXZlIFRpbWUgc2VyaWVzIHBsb3QpDQp0bXAgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShkZiRUaWNrZXQuRGF0ZSkpOw0KZG9uIDwtIHh0cyh4PXRtcCRGcmVxLCBvcmRlci5ieT15bWQodG1wJFZhcjEpKTsgIyBDcmVhdGUgdGhlIHh0cyBmb3JtYXQgDQpkeWdyYXBoKGRvbiwgbWFpbj0iRGFpbHkgVGlja2V0cyBJc3N1ZWQgKDIwMTYtMjAxNykiKSAlPiUNCiAgZHlPcHRpb25zKGxhYmVsc1VUQz0gVFJVRSwgZmlsbEdyYXBoPVRSVUUsIGZpbGxBbHBoYT0wLjEsIGRyYXdHcmlkID0gVFJVRSwgY29sb3JzPSIjRDhBRTVBIikgJT4lDQogIGR5TGltaXQobWF4KHRtcCRGcmVxKSwgbGFiZWw9cGFzdGUwKCJNQVg9IixtYXgodG1wJEZyZXEpKSwgbGFiZWxMb2M9ImxlZnQiKSAlPiUNCiAgZHlMaW1pdChtZWFuKHRtcCRGcmVxKSxsYWJlbD1wYXN0ZTAoIkFWRz0iLGFzLmludGVnZXIobWVhbih0bXAkRnJlcSkpKSxsYWJlbExvYz0ibGVmdCIpICU+JQ0KICBkeUxpbWl0KG1pbih0bXAkRnJlcSksIGxhYmVsPXBhc3RlMCgiTUlOPSIsbWluKHRtcCRGcmVxKSksIGxhYmVsTG9jPSJsZWZ0IikgJT4lDQogIGR5UmFuZ2VTZWxlY3RvcigpICU+JQ0KICBkeUNyb3NzaGFpcihkaXJlY3Rpb24gPSAiYm90aCIpICU+JQ0KICBkeUhpZ2hsaWdodChoaWdobGlnaHRDaXJjbGVTaXplPTUsIGhpZ2hsaWdodFNlcmllc0JhY2tncm91bmRBbHBoYT0wLjIsIGhpZGVPbk1vdXNlT3V0PUZBTFNFKSAgJT4lDQogIGR5Um9sbGVyKHJvbGxQZXJpb2QgPSAxKTsNCnJtKHRtcCwgZG9uKTsgIyBDbGVhbiB1cA0KYGBgDQojIyBHZW8tc3BhdGlhbCBDdXN0ZXIgQW5hbHlzaXMNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQojIyBQbG90IHVzaW5nIExlYWZsZXQgKGdlby1zcGF0aWFsIGN1c3RlciBhbmFseXNpcykNCnRvcG8gPC0gcmVhZExpbmVzKGluZmlsZV9qc29uKSAlPiUNCiAgcGFzdGUoY29sbGFwc2UgPSAiXG4iKTsNCnA2IDwtIGxlYWZsZXQoZGF0YT1nZW9jb2RlZCkgICAgICAgICAgICAgICAgICAgICAlPiUgDQogIGFkZFRpbGVzKCkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJT4lIA0KICBhZGRSZWN0YW5nbGVzKA0KICAgIGxuZzE9bG9uQm94WzFdLCBsYXQxPWxhdEJveFsxXSwNCiAgICBsbmcyPWxvbkJveFsyXSwgbGF0Mj1sYXRCb3hbMl0sDQogICAgZmlsbENvbG9yID0gInRyYW5zcGFyZW50IikgICAgICAgICAgICAgICAgICAgJT4lDQogIGFkZFRvcG9KU09OKHRvcG8sDQogICAgd2VpZ2h0ID0gMSwNCiAgICBjb2xvciAgPSAiIzQ0NDQ0NCIsDQogICAgZmlsbCAgID0gVFJVRSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJFN0YW1lbi5Ub25lckxpbmVzLA0KICAgIG9wdGlvbnMgPSBwcm92aWRlclRpbGVPcHRpb25zKG9wYWNpdHk9MC4zNSkpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRTdGFtZW4uVG9uZXIpICAgICAgICU+JQ0KICBhZGRDaXJjbGVNYXJrZXJzKH5sb24sIH5sYXQsDQogICAgY2x1c3Rlck9wdGlvbnMgPSBtYXJrZXJDbHVzdGVyT3B0aW9ucygpLA0KICAgIGNvbG9yICAgICAgICAgID0gfmdldENvbG9yKGZyZXEpLA0KICAgIHN0cm9rZSAgICAgICAgID0gVFJVRSwNCiAgICBmaWxsT3BhY2l0eSAgICA9IDAuMjUsDQogICAgcG9wdXAgICAgICAgICAgPSB+YXMuY2hhcmFjdGVyKGFkZHIpKTsNCmh0bWx0b29sczo6c2F2ZV9odG1sKHA2LCBmaWxlID0gImdlb2NvZGVkLmh0bWwiKTsNCnJtKHA2KTsgICMgQ2xlYW4gVXANCmBgYA0KIA0KYGBge3IgZWNobz1UUlVFLCBmaWcuaGVpZ2h0PTguNSwgZmlnLndpZHRoPTExLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBvdXQud2lkdGg9IjkwJSJ9DQpsZWFmbGV0KGRhdGE9Z2VvY29kZWQpICU+JSANCiAgYWRkVGlsZXMoKSAlPiUgDQogIGFkZFJlY3RhbmdsZXMoDQogICAgbG5nMT1sb25Cb3hbMV0sIGxhdDE9bGF0Qm94WzFdLA0KICAgIGxuZzI9bG9uQm94WzJdLCBsYXQyPWxhdEJveFsyXSwNCiAgICBmaWxsQ29sb3IgPSAidHJhbnNwYXJlbnQiKSAlPiUNCiAgYWRkVG9wb0pTT04odG9wbywNCiAgICB3ZWlnaHQgPSAxLA0KICAgIGNvbG9yICA9ICIjNDQ0NDQ0IiwNCiAgICBmaWxsICAgPSBUUlVFKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAlPiUNCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkU3RhbWVuLlRvbmVyTGluZXMsDQogICAgb3B0aW9ucyA9IHByb3ZpZGVyVGlsZU9wdGlvbnMob3BhY2l0eT0wLjM1KSkgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJFN0YW1lbi5Ub25lcikgICAgICAgJT4lDQogIGFkZENpcmNsZU1hcmtlcnMofmxvbiwgfmxhdCwNCiAgICBjb2xvciAgICAgICAgICA9IH5nZXRDb2xvcihmcmVxKSwNCiAgICBzdHJva2UgICAgICAgICA9IFRSVUUsDQogICAgZmlsbE9wYWNpdHkgICAgPSAwLjI1LA0KICAgIHBvcHVwICAgICAgICAgID0gfmFzLmNoYXJhY3RlcihhZGRyKSk7DQpgYGANCg0KYGBge3J9DQpybSh0b3BvKTsgICMgQ2xlYW4gVXANCmBgYA0KDQo=