├── .gitignore
├── html_button.html
├── toggle_script.js
├── warehouse_location.Rproj
├── google-analytics.js
├── gomap.js
├── global.R
├── README.md
├── styles.css
├── server.R
└── ui.R
/.gitignore:
--------------------------------------------------------------------------------
1 | .Rproj.user
2 | .Rhistory
3 | .RData
4 | .Ruserdata
5 | api_key
6 | data/*
7 |
--------------------------------------------------------------------------------
/html_button.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/toggle_script.js:
--------------------------------------------------------------------------------
1 | function myFunction() {
2 | var x = document.getElementById("controls");
3 | if (x.style.display === "none") {
4 | x.style.display = "block";
5 | } else {
6 | x.style.display = "none";
7 | }
8 | }
--------------------------------------------------------------------------------
/warehouse_location.Rproj:
--------------------------------------------------------------------------------
1 | Version: 1.0
2 |
3 | RestoreWorkspace: Default
4 | SaveWorkspace: Default
5 | AlwaysSaveHistory: Default
6 |
7 | EnableCodeIndexing: Yes
8 | UseSpacesForTab: Yes
9 | NumSpacesForTab: 2
10 | Encoding: UTF-8
11 |
12 | RnwWeave: Sweave
13 | LaTeX: pdfLaTeX
14 |
--------------------------------------------------------------------------------
/google-analytics.js:
--------------------------------------------------------------------------------
1 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
2 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();
3 | a=s.createElement(o), m=s.getElementsByTagName(o)[0];
4 | a.async=1;
5 | a.src=g;m.parentNode.insertBefore(a,m);
6 | })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
7 |
8 | ga('create', 'UA-129389361-1', 'auto');
9 | ga('send', 'pageview');
--------------------------------------------------------------------------------
/gomap.js:
--------------------------------------------------------------------------------
1 | // When locator icon in datatable is clicked, go to that spot on the map
2 | $(document).on("click", ".go-map", function(e) {
3 | e.preventDefault();
4 | $el = $(this);
5 | var lat = $el.data("lat");
6 | var long = $el.data("long");
7 | var zip = $el.data("zip");
8 | $($("#nav a")[0]).tab("show");
9 | Shiny.onInputChange("goto", {
10 | lat: lat,
11 | lng: long,
12 | zip: zip,
13 | nonce: Math.random()
14 | });
15 | });
--------------------------------------------------------------------------------
/global.R:
--------------------------------------------------------------------------------
1 | library(dplyr)
2 | require(curl)
3 |
4 | get_place_name<-function(lng,lat,api_key){
5 | base_url<-"https://maps.googleapis.com/maps/api/geocode/json?"
6 | latlng<-paste0("latlng=",lat,",",lng)
7 | sensor<-paste0("sensor=false")
8 | key<-paste0("key=",api_key)
9 | result_type<-paste0("result_type=administrative_area_level_2")
10 |
11 | urls<-paste(paste0(base_url,latlng),sensor,key,result_type,sep="&")
12 | res<-NULL
13 | for(url in urls){
14 | result<-jsonlite::fromJSON(curl(url))
15 | res<-c(res,if_else(is.null(result$results$formatted_address),"Loc Not found",result$results$formatted_address))
16 | }
17 |
18 | return(res)
19 |
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # warehouse_loc_determination
2 | Shiny App for Warehouse location Determination
3 |
4 | This shiny app try to find the best warehousing location based on the demand pattern. For determining the suitable warehousing location
5 | it try to keep the warehouse near the demand to optimize the service levels and outbound logistics cost.
6 |
7 | You can visit http://yklearn.com/shiny/wh_loc_finder to see the demo of this app.
8 |
9 | Structure of the file for loading in the application.
10 | To simulate your scenario you can upload your demand pattern in the app. Don't worry we will not store the file you have uploaded. It exists only till your session is live.
11 |
12 | You can download the template by clicking on download template button.
13 |
14 | When you upload the demand using the csv file. It will be plotted on the map. Here you can see and analyze the geograohical pattern of your demand.
15 |
16 | Select the desired no of warehouse via using the slider.
17 | Next thing to do is to cluster these demand via `K-means` clustering there you may need to wait a while to finish the clustering process.
18 |
19 | ```
20 | Note: Kmeans clustering algorithm has random initialization method to control this you can change the seed value.
21 | ```
22 | Once clusterin is done you can find out the suitable warehousing locations by clicking on `Run COG` button.
23 |
24 |
25 | To contribute and modify this app clone the repository by typing
26 |
27 | ```
28 | git clone https://github.com/yogihbti/warehouse_loc_determination.git
29 | ```
30 | To get the warehouse location names you need to add the google api_key in the file named api_key. This app will read the file to access the google API.
31 |
32 |
33 | FAQ
34 | 1. How to get the google API Key?
35 | 2. How do i clone this repository?
36 | 3. R packages needed to be installed.
37 |
--------------------------------------------------------------------------------
/styles.css:
--------------------------------------------------------------------------------
1 | input[type="number"] {
2 | max-width: 80%;
3 | }
4 |
5 | .outer {
6 | position: fixed;
7 | top: 41px;
8 | left: 0;
9 | right: 0;
10 | bottom: 0;
11 | overflow: hidden;
12 | padding: 0;
13 | }
14 |
15 | /* Customize fonts */
16 | body, label, input, button, select {
17 | font-family: 'Helvetica Neue', Helvetica;
18 | font-weight: 200;
19 | }
20 | h1, h2, h3, h4 { font-weight: 400; }
21 | #toggle_bt {
22 | /* Appearance */
23 | background-color: transparent;
24 | padding: 0 20px 20px 20px;
25 | cursor: move;
26 | /* Fade out while not hovering */
27 | border-color: transparent;
28 |
29 | }
30 |
31 |
32 | #controls {
33 | /* Appearance */
34 | background-color: white;
35 | padding: 0 20px 20px 20px;
36 | cursor: move;
37 | /* Fade out while not hovering */
38 | opacity: 0.65;
39 | zoom: 0.9;
40 | transition: opacity 500ms 1s;
41 | }
42 |
43 | #controls:hover {
44 | /* Fade in while hovering */
45 | opacity: 0.95;
46 | transition-delay: 0;
47 | }
48 |
49 | /* Position and style citation */
50 | #cite {
51 | position: absolute;
52 | bottom: 10px;
53 | left: 10px;
54 | font-size: 12px;
55 | }
56 |
57 | /* If not using map tiles, show a white background */
58 | .leaflet-container {
59 | background-color: white !important;
60 | }
61 |
62 | /* The switch - the box around the slider */
63 | .switch {
64 | position: relative;
65 | display: inline-block;
66 | width: 60px;
67 | height: 34px;
68 | }
69 |
70 | /* Hide default HTML checkbox */
71 | .switch input {
72 | opacity: 0;
73 | width: 0;
74 | height: 0;
75 | }
76 |
77 | /* The slider */
78 | .slider {
79 | position: absolute;
80 | cursor: pointer;
81 | top: 0;
82 | left: 0;
83 | right: 0;
84 | bottom: 0;
85 | background-color: #ccc;
86 | -webkit-transition: .4s;
87 | transition: .4s;
88 | }
89 |
90 | .slider:before {
91 | position: absolute;
92 | content: "";
93 | height: 26px;
94 | width: 26px;
95 | left: 4px;
96 | bottom: 4px;
97 | background-color: white;
98 | -webkit-transition: .4s;
99 | transition: .4s;
100 | }
101 |
102 | input:checked + .slider {
103 | background-color: #2196F3;
104 | }
105 |
106 | input:focus + .slider {
107 | box-shadow: 0 0 1px #2196F3;
108 | }
109 |
110 | input:checked + .slider:before {
111 | -webkit-transform: translateX(26px);
112 | -ms-transform: translateX(26px);
113 | transform: translateX(26px);
114 | }
115 |
116 | /* Rounded sliders */
117 | .slider.round {
118 | border-radius: 34px;
119 | }
120 |
121 | .slider.round:before {
122 | border-radius: 50%;
123 | }
124 |
125 | .mobileShow {display: none;}
126 |
127 | /* Smartphone Portrait and Landscape */
128 | @media only screen
129 | and (min-device-width : 320px)
130 | and (max-device-width : 480px){
131 | .mobileShow {display: inline;}
132 | }
--------------------------------------------------------------------------------
/server.R:
--------------------------------------------------------------------------------
1 | library(leaflet)
2 | library(RColorBrewer)
3 | library(scales)
4 | library(lattice)
5 | library(dplyr)
6 |
7 |
8 | function(input, output, session) {
9 |
10 | #Define Reactive values
11 | data_read<-reactiveVal(NULL)
12 | cluster_df<-reactiveVal(NULL)
13 | ## Interactive Map ###########################################
14 |
15 | # Create the map
16 | # Tiles selected for Indian region
17 | output$map <- renderLeaflet({
18 | leaflet() %>%
19 | addTiles(
20 | urlTemplate = "https://{s}.tiles.mapbox.com/v4/openstreetmap.1b68f018/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoiamluYWxmb2ZsaWEiLCJhIjoiY2psejFtZG8wMWhnMjNwcGFqdTNjaGF2MCJ9.ZQVAZAw8Xtg4H2YSuG4PlA",
21 | attribution = '© OpenStreetMap contributors by Mapbox'
22 | ) %>%
23 | setView(lng = 79.85, lat = 21.14, zoom = 4)
24 | })
25 |
26 | # Download file handler
27 | output$downloadData <- downloadHandler(
28 | filename = "geo_data_input.csv",
29 | content = function(file) {
30 | write.csv(data.frame(Longitude="",Latitude="",SalesVal=0),
31 | file, row.names = FALSE)
32 | }
33 | )
34 |
35 | #load data from file to a reactive variable
36 |
37 | data_read <- reactive({
38 | #req(input$file1) ## ?req # require that the input is available
39 | #req(input$sample_or_own)
40 | #browser()
41 | inFile <- input$file1
42 | if(input$sample_or_own=="Yes"){
43 | df <- read.csv("./data/geo_data_input.csv", header = TRUE)
44 |
45 | }else if(is.null(inFile)){
46 |
47 | df<-data.frame(Longitude=numeric(),Latitude=numeric(),SalesVal=numeric())
48 |
49 | } else{
50 | df <- read.csv(inFile$datapath, header = TRUE)
51 | }
52 |
53 | return(df)
54 |
55 | })
56 |
57 | #observe for input file and reactive value to be available to update the map
58 |
59 | observeEvent( {input$file1
60 | input$sample_or_own
61 | }
62 | ,{
63 | leafletProxy("map", data = data_read()) %>%
64 | clearShapes() %>%
65 | clearMarkers() %>%
66 | addCircles(lng=~Longitude, lat=~Latitude, radius=~log(SalesVal)*5000,fillOpacity=.4,weight = 1 ,color="orange")
67 | },
68 | ignoreNULL = FALSE
69 |
70 | )
71 |
72 |
73 |
74 |
75 | #observe the clustering button clicked- do the clustering to set color groups
76 | observeEvent(input$run_cluster,{
77 | set.seed(input$seed)
78 | df<-data_read()
79 | df_matrix<-df[,c("Latitude","Longitude")]
80 | cluster_geo=kmeans(df_matrix,input$no_of_wh)
81 | df$cluster<-as.factor(cluster_geo$cluster)
82 | pal<-colorFactor("viridis",df$cluster)
83 | #browser()
84 | leafletProxy("map", data = df) %>%
85 | clearShapes() %>%
86 | clearMarkers() %>%
87 | addCircles(lng=~Longitude, lat=~Latitude,
88 | radius=~log(SalesVal)*5000,
89 | fillOpacity=.4,
90 | weight = 1,
91 | color=~pal(cluster),group=~cluster)
92 | cluster_df(df)
93 |
94 | })
95 |
96 | observeEvent(input$find_cog,{
97 | if(!is.null(input$run_cluster)){
98 | api_key<-readLines("api_key")
99 | df<-cluster_df()
100 | cog_center<- df %>%
101 | group_by(cluster) %>%
102 | summarise(lon_center=weighted.mean(Longitude,SalesVal),
103 | lat_center=weighted.mean(Latitude,SalesVal))
104 | place_name<-get_place_name(cog_center$lon_center,cog_center$lat_center,api_key)
105 | cog_center$place_name<-place_name
106 |
107 | pal<-colorFactor("viridis",df$cluster)
108 | #browser()
109 | leafletProxy("map", data = df) %>%
110 | clearShapes() %>%
111 | clearMarkers() %>%
112 | addCircles(lng=~Longitude, lat=~Latitude,
113 | radius=~log(SalesVal)*5000,
114 | fillOpacity=.4,
115 | weight = 1,color=~pal(cluster)) %>%
116 | addMarkers(lng=~lon_center,lat=~lat_center,popup=~place_name,data =cog_center)
117 | }else{
118 | showNotification("First upload a file.")
119 | }
120 |
121 | })
122 |
123 | }
--------------------------------------------------------------------------------
/ui.R:
--------------------------------------------------------------------------------
1 | library(leaflet)
2 | library(shiny)
3 |
4 |
5 |
6 | navbarPage("Warehouse Determination", id="nav",
7 |
8 | tabPanel("Interactive map",
9 | div(class="outer",
10 |
11 | tags$head(
12 | # Include our custom CSS
13 | includeCSS("styles.css"),
14 | includeScript("google-analytics.js"),
15 | includeScript("gomap.js"),
16 | includeScript("toggle_script.js")
17 | ),
18 |
19 | # If not using custom CSS, set height of leafletOutput to a number instead of percent
20 | leafletOutput("map", width="100%", height="100%"),
21 |
22 | absolutePanel(id = "toggle_bt", class = "panel panel-default mobileShow", fixed = TRUE,
23 | draggable = TRUE, top =100, left = 120, right = "auto", bottom = "auto",
24 | width = 120, height = "auto",
25 | includeHTML('html_button.html')
26 |
27 | ),
28 |
29 |
30 | # Shiny versions prior to 0.11 should use class = "modal" instead.
31 | absolutePanel(id = "controls", class = "panel panel-default", fixed = TRUE,
32 | draggable = TRUE, top = 160, left = 20, right = "auto", bottom = "auto",
33 | width = 330, height = "auto",
34 | h4("Find Best Warehousing Locations"),
35 | radioButtons("sample_or_own","I Want to use sample data.",
36 | choices = c("Yes","No"),
37 | selected = "No",
38 | inline=T),
39 | conditionalPanel("input.sample_or_own == 'No'",
40 | h4("Download "),
41 | downloadButton("downloadData", "Download the File Template"),
42 | fileInput("file1", "Choose CSV File",
43 | accept = c(
44 | "text/csv",
45 | "text/comma-separated-values,text/plain",
46 | ".csv")
47 | )),
48 |
49 | tags$hr(),
50 | sliderInput(inputId = "no_of_wh",label = "No of Warehouse",value=4,min = 1,max=10),
51 | numericInput("seed","Seed for K-Means",100,min=0,max=101),
52 | actionButton("run_cluster","Run Clustering"),
53 | tags$hr(),
54 | actionButton("find_cog","Find COG Locations")
55 |
56 |
57 | )
58 |
59 | )
60 | )
61 |
62 | # tabPanel("Data explorer",
63 | # fluidRow(
64 | # column(3,
65 | # selectInput("states", "States", c("All states"="", structure(state.abb, names=state.name), "Washington, DC"="DC"), multiple=TRUE)
66 | # ),
67 | # column(3,
68 | # conditionalPanel("input.states",
69 | # selectInput("cities", "Cities", c("All cities"=""), multiple=TRUE)
70 | # )
71 | # ),
72 | # column(3,
73 | # conditionalPanel("input.states",
74 | # selectInput("zipcodes", "Zipcodes", c("All zipcodes"=""), multiple=TRUE)
75 | # )
76 | # )
77 | # ),
78 | # fluidRow(
79 | # column(1,
80 | # numericInput("minScore", "Min score", min=0, max=100, value=0)
81 | # ),
82 | # column(1,
83 | # numericInput("maxScore", "Max score", min=0, max=100, value=100)
84 | # )
85 | # ),
86 | # hr(),
87 | # DT::dataTableOutput("ziptable")
88 | # ),
89 |
90 | )
--------------------------------------------------------------------------------