class: center, middle, inverse, title-slide # First Steps on Using R as a GIS ## Data Processing & Spatial Linking ### Stefan Jünger
July 08, 2021 --- layout: true --- ## Now <table class="table" style="margin-left: auto; margin-right: auto;"> <thead> <tr> <th style="text-align:left;"> Time </th> <th style="text-align:left;"> Title </th> </tr> </thead> <tbody> <tr> <td style="text-align:left;"> 01:00pm-01:20pm </td> <td style="text-align:left;"> Introduction </td> </tr> <tr> <td style="text-align:left;"> 01:20pm-01:30pm </td> <td style="text-align:left;"> Exercise 1: R Warm up </td> </tr> <tr> <td style="text-align:left;background-color: yellow !important;"> 01:30pm-02:00pm </td> <td style="text-align:left;background-color: yellow !important;"> Data Processing & Spatial Linking </td> </tr> <tr> <td style="text-align:left;"> 02:00pm-02:30pm </td> <td style="text-align:left;"> Exercise 2: Geospatial Data Wrangling </td> </tr> <tr> <td style="text-align:left;"> 02:30pm-02:45pm </td> <td style="text-align:left;"> Break </td> </tr> <tr> <td style="text-align:left;"> 02:45pm-03:15pm </td> <td style="text-align:left;"> Easy Maps </td> </tr> <tr> <td style="text-align:left;"> 03:15pm-03:45pm </td> <td style="text-align:left;"> Excercise 3: Build your own map </td> </tr> <tr> <td style="text-align:left;"> 03:45pm-04:00pm </td> <td style="text-align:left;"> Closing, Q & A </td> </tr> </tbody> </table> --- ## Why Care About Data Types and Formats? .pull-left[ - Different commands to import and handle the data - Spatial linking techniques and analyses partially determined by the data format - Visualization of data can differ ] .pull-right[ <img src="data:image/png;base64,#C:\Users\mueller2\talks_presentations\esra-workshop-first-steps-R-GIS\content\img\layers.png" width="100%" style="display: block; margin: auto;" /> ] --- ## Visual Difference of Vector and Raster Data <img src="data:image/png;base64,#C:\Users\mueller2\talks_presentations\esra-workshop-first-steps-R-GIS\content\img\fig_geometries.png" width="40%" style="display: block; margin: auto;" /> --- ## More Formally, What Are... .pull-left[ **...Vector Data?** - Each real-world feature is represented as one of three types of geometries: - Points: discrete location (f.e., city) - Lines: linear feature (f.e., river) - Polygons: enclosed area (f.e., country) - Geometries are not fixed ] -- .pull-right[ **...Raster Data?** - Hold information on (most of the time) evenly shaped grid cells - Basically, a simple data table - each cell represents one observation ] --- ## Vector Data's Main Characteristic <br> .pull-left[ Geometries' 3 types of information: - location (points, lines, and polygons) - length (lines and polygons) - area (polygons) ] .pull-right[ We need to assign attributes to each geometry: - attribute tables (i.e., data tables). - rows = geometric object (i.e., observation, case, feature) - column = attribute (i.e., variable) ] --- class: middle <br> <img src="data:image/png;base64,#C:\Users\mueller2\talks_presentations\esra-workshop-first-steps-R-GIS\content\img\attr_table.png" width="85%" style="display: block; margin: auto;" /> --- ## Raster Data's Main Characteristic <br> - Information about geometries are globally stored - their location in space is defined by their cell location in the data table - information about raster cells' resolution are stored as metadata - Usually, rasters only comprise one attribute - one raster layer can comprise several bands though (e.g., rgb-bands) --- class: middle <img src="data:image/png;base64,#C:\Users\mueller2\talks_presentations\esra-workshop-first-steps-R-GIS\content\img\table_to_raster.png" width="85%" style="display: block; margin: auto;" /> --- ## File Formats <br> <br> .pull-left[ **Vector Data** - Shapefile (.shp & Co) - .geojson - ... - .kml - .gml ] -- .pull-right[ **Raster Data** - Gtiff/GeoTiff - JPEG2000 - ... - .grd - netCDF ] --- ## Importing Data: We Only Need Two Commands <br> .pull-left[ **Vector data** ```r sf::st_read() ``` ] -- .pull-right[ **Raster data** ```r stars::read_stars() ``` ] --- ## Reading Vector Data in Practise: COVID-19 Cases in Cologne ```r corona_cologne <- sf::read_sf( "./data/corona_cologne.shp" ) corona_cologne ``` ``` ## Simple feature collection with 86 features and 8 fields ## Geometry type: MULTIPOLYGON ## Dimension: XY ## Bounding box: xmin: 6.77253 ymin: 50.83045 xmax: 7.162028 ymax: 51.08496 ## Geodetic CRS: WGS 84 ## # A tibble: 86 x 9 ## id name anzhl_7 a_7_100 anzhl_g anzhl_k einwhnr stand geometry ## <int> <chr> <int> <dbl> <int> <int> <int> <chr> <MULTIPOLYGON [°]> ## 1 1 Marie~ 0 0 299 1 7258 23.06~ (((6.971316 50.90478, 6.971546 5~ ## 2 2 Bayen~ 2 19.2 400 2 10426 23.06~ (((6.973431 50.91707, 6.975959 5~ ## 3 3 Weiß 0 0 152 0 5907 23.06~ (((7.046571 50.89097, 7.047746 5~ ## 4 4 Hahnw~ 0 0 61 0 2066 23.06~ (((6.969714 50.88297, 6.970253 5~ ## 5 5 Mesch~ 4 49.9 618 6 8024 23.06~ (((6.925416 50.87073, 6.927486 5~ ## 6 6 Immen~ 0 0 71 1 2062 23.06~ (((6.955817 50.86776, 6.955897 5~ ## 7 7 Godorf 1 36.6 158 4 2729 23.06~ (((6.994359 50.85836, 6.994369 5~ ## 8 8 Löven~ 0 0 230 3 9186 23.06~ (((6.835101 50.95726, 6.83883 50~ ## 9 9 Weiden 3 17.2 800 4 17453 23.06~ (((6.849502 50.9422, 6.84951 50.~ ## 10 10 Junke~ 0 0 483 0 15399 23.06~ (((6.854198 50.94052, 6.854394 5~ ## # ... with 76 more rows ``` --- ## The `geometry` Column ```r corona_cologne["geometry"] ``` ``` ## Simple feature collection with 86 features and 0 fields ## Geometry type: MULTIPOLYGON ## Dimension: XY ## Bounding box: xmin: 6.77253 ymin: 50.83045 xmax: 7.162028 ymax: 51.08496 ## Geodetic CRS: WGS 84 ## # A tibble: 86 x 1 ## geometry ## <MULTIPOLYGON [°]> ## 1 (((6.971316 50.90478, 6.971546 50.90483, 6.971546 50.90483, 6.971546 50.90483, 6.971... ## 2 (((6.973431 50.91707, 6.975959 50.91287, 6.979068 50.90879, 6.979459 50.9083, 6.9797... ## 3 (((7.046571 50.89097, 7.047746 50.89014, 7.048239 50.88973, 7.048942 50.88909, 7.049... ## 4 (((6.969714 50.88297, 6.970253 50.88073, 6.970377 50.88074, 6.970513 50.8806, 6.9708... ## 5 (((6.925416 50.87073, 6.927486 50.86942, 6.927528 50.86939, 6.92764 50.86935, 6.9285... ## 6 (((6.955817 50.86776, 6.955897 50.86773, 6.955938 50.86775, 6.958341 50.86696, 6.960... ## 7 (((6.994359 50.85836, 6.994369 50.85836, 6.994383 50.85835, 6.994429 50.85834, 6.994... ## 8 (((6.835101 50.95726, 6.83883 50.95419, 6.842222 50.95549, 6.842611 50.9551, 6.84289... ## 9 (((6.849502 50.9422, 6.84951 50.94038, 6.849356 50.93803, 6.849289 50.93654, 6.84924... ## 10 (((6.854198 50.94052, 6.854394 50.94052, 6.854395 50.94055, 6.854396 50.94056, 6.854... ## # ... with 76 more rows ``` --- ## Reading Vector Data in Practise: Hospitals in Cologne ```r hospitals_cologne <- sf::read_sf( "./data/hospitals_cologne.shp" ) hospitals_cologne ``` ``` ## Simple feature collection with 35 features and 10 fields ## Geometry type: POINT ## Dimension: XY ## Bounding box: xmin: 6.897622 ymin: 50.88123 xmax: 7.056884 ymax: 50.99195 ## Geodetic CRS: WGS 84 ## # A tibble: 35 x 11 ## OBJECTI OBJEKTN NAME NUTZUNG ADRESSE_ ADRESSE STADTBE STADTTE STADTVI POSTZUS ## <int> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> ## 1 1 11510005 Eduardus-~ Kranken~ 00663000~ Custodisst~ Innens~ Deutz Germanen~ 50679 ## 2 2 11510016 Ev. Krank~ Kranken~ 03310007~ Weyertal 76 Linden~ Lindent~ Uni-Vier~ 50931 ## 3 3 11510028 MediaPark~ Kranken~ 03724000~ Im Mediapa~ Innens~ Neustad~ Media-Pa~ 50670 ## 4 4 11510013 Krankenha~ Kranken~ 02436020~ Ostmerheim~ Kalk Merheim GE Merhe~ 51109 ## 5 5 11510022 Klinik am~ Kranken~ 01472002~ Hohenstauf~ Innens~ Neustad~ Studente~ 50674 ## 6 6 11510031 RehaNova Kranken~ 02436020~ Ostmerheim~ Kalk Merheim GE Merhe~ 51109 ## 7 8 11510009 Universit~ Kranken~ 01675000~ Joseph-Ste~ Linden~ Lindent~ Uni-Vier~ 50931 ## 8 9 11510018 Krankenha~ Kranken~ 05620001~ Urbacher W~ Porz Porz GE Porz 51149 ## 9 10 11510029 PAN Klini~ Kranken~ 03405000~ Zeppelinst~ Innens~ Altstad~ Neumarkt~ 50667 ## 10 11 11510017 Alexianer~ Kranken~ 04995006~ Kölner Str~ Porz Ensen Ensen-Ost 51149 ## # ... with 25 more rows, and 1 more variable: geometry <POINT [°]> ``` --- ## The `geometry` Column ```r hospitals_cologne["geometry"] ``` ``` ## Simple feature collection with 35 features and 0 fields ## Geometry type: POINT ## Dimension: XY ## Bounding box: xmin: 6.897622 ymin: 50.88123 xmax: 7.056884 ymax: 50.99195 ## Geodetic CRS: WGS 84 ## # A tibble: 35 x 1 ## geometry ## <POINT [°]> ## 1 (6.980327 50.93277) ## 2 (6.925405 50.92739) ## 3 (6.94752 50.94856) ## 4 (7.050221 50.93935) ## 5 (6.941231 50.93114) ## 6 (7.050371 50.93951) ## 7 (6.918107 50.92434) ## 8 (7.052081 50.89247) ## 9 (6.948554 50.937) ## 10 (7.042189 50.89845) ## # ... with 25 more rows ``` --- ## How Do I Know They Are Vectors? First Check: Plot Layers With `plot()`! .pull-left[ ```r plot(corona_cologne["anzhl_g"]) ``` <img src="data:image/png;base64,#2_Data_Processing_Linking_files/figure-html/corona-map-1.png" style="display: block; margin: auto;" /> ] -- .pull-right[ ```r plot(hospitals_cologne["STADTBE"]) ``` <img src="data:image/png;base64,#2_Data_Processing_Linking_files/figure-html/hospitals-map-1.png" style="display: block; margin: auto;" /> ] --- ## Is the CRS correctly assigned? Plot them with `mapview::mapview()`! .pull-left[ ```r mapview::mapview( corona_cologne["anzhl_g"] ) ``` ] .pull-right[
] --- ## ...And the Hospitals .pull-left[ ```r mapview::mapview( hospitals_cologne["STADTBE"] ) ``` ] .pull-right[ ] --- ## Creating New Variables .pull-left[ ```r corona_cologne <- corona_cologne %>% dplyr::mutate( incidence = (anzhl_g / einwhnr) * 100 ) ``` ] -- .pull-right[ <img src="data:image/png;base64,#2_Data_Processing_Linking_files/figure-html/incidence-plot-1.png" style="display: block; margin: auto;" /> ] --- ## Geometric Operations and Spatial Linking The main advantage of geospatial data is the flexibility of their geometric properties. They make spatial linking of geospatial data straightforward. <img src="data:image/png;base64,#C:\Users\mueller2\talks_presentations\esra-workshop-first-steps-R-GIS\content\img\fig_3d_simple.png" width="50%" style="display: block; margin: auto;" /> --- ## Linking Hospitals to Our Corona Data How many hospitals are located in each of Cologne's districts? How would we do that? - We project the corona and hospital data in one space - We extract the hospitals' information and transfer it to the corona data by counting how many of them fall into the corona datas' polygons -- (...but first, we first have to check whether our data are valid. ```r corona_cologne <- sf::st_make_valid(corona_cologne) hospitals_cologne <- sf::st_make_valid(hospitals_cologne) ``` ) --- ## Detect Containing Geometries .pull-left[ ```r containing <- sf::st_contains( corona_cologne, hospitals_cologne ) ``` ] -- .pull-right[ ``` ## Sparse geometry binary predicate list of length 86, where the predicate was ## `contains' ## first 23 elements: ## 1: (empty) ## 2: 11 ## 3: (empty) ## 4: (empty) ## 5: (empty) ## 6: (empty) ## 7: (empty) ## 8: (empty) ## 9: (empty) ## 10: (empty) ## 11: (empty) ## 12: (empty) ## 13: (empty) ## 14: (empty) ## 15: (empty) ## 16: (empty) ## 17: (empty) ## 18: (empty) ## 19: (empty) ## 20: (empty) ## 21: 28 ## 22: (empty) ## 23: 4, 6, 29 ``` ] --- ## Counting Containing Geometries ```r containing_count <- lengths(containing) containing_count ``` ``` ## [1] 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 3 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 ## [47] 2 3 2 3 2 0 0 3 1 0 0 1 0 1 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 4 0 0 0 1 0 1 0 ``` -- We then can add this information as a new variable: ```r corona_cologne <- corona_cologne %>% dplyr::mutate( hospitals_count = containing_count ) ``` --- ## Incidence vs. Hospital Infrastructure .pull-left[ ```r plot( corona_cologne[ c("incidence", "hospitals_count") ] ) ``` ] .pull-right[ <img src="data:image/png;base64,#2_Data_Processing_Linking_files/figure-html/incidence-and-hospitals-map-exec-1.png" style="display: block; margin: auto;" /> ] --- ## There Are More Geometric Confirmation Methods <style type="text/css"> .rotate { transform: rotate(-90deg); text-align: left; } </style> .pull-left[ <img src="data:image/png;base64,#C:\Users\mueller2\talks_presentations\esra-workshop-first-steps-R-GIS\content\img\geometric_confirmation.png" width="75%" style="display: block; margin: auto;" /> ] .pull-right[ <br> <br> .rotate[ .tinyisher[ https://github.com/rstudio/cheatsheets/blob/master/sf.pdf ] ] ] --- ## Turning to Raster Data: Information About Immigrant Shares Covid-19 is a disease disproportionaly affecting social groups. For example, members of minority groups may be at a higher risk to get infected. We are going to try to 'corroborate' this finding with German Census 2011 *raster* data. **However: This is by no means a serious research assessment!** --- ## Loading Raster Data: Immigrants From the German Census 2011 ```r immigrants_cologne <- stars::read_stars("./data/immigrants_cologne.tif") immigrants_cologne ``` ``` ## stars object with 2 dimensions and 1 attribute ## attribute(s): ## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's ## immigrants_cologne.tif 3 3 7 15.1337 17 639 62429 ## dimension(s): ## from to offset delta refsys point values x/y ## x 1 264 4094850 100 ETRS89-extended / LAEA Eu... FALSE NULL [x] ## y 1 289 3112950 -100 ETRS89-extended / LAEA Eu... FALSE NULL [y] ``` --- ## Loading Raster Data: Inhabitants From the German Census 2011 ```r inhabitants_cologne <- stars::read_stars("./data/inhabitants_cologne.tif") inhabitants_cologne ``` ``` ## stars object with 2 dimensions and 1 attribute ## attribute(s): ## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's ## inhabitants_cologne.tif 3 20 44 62.53469 81 956 52731 ## dimension(s): ## from to offset delta refsys point values x/y ## x 1 264 4094850 100 ETRS89-extended / LAEA Eu... FALSE NULL [x] ## y 1 289 3112950 -100 ETRS89-extended / LAEA Eu... FALSE NULL [y] ``` --- ## Compare Layers by Plotting .pull-left[ ```r plot(immigrants_cologne) ``` <img src="data:image/png;base64,#2_Data_Processing_Linking_files/figure-html/plot-immigrants-1.png" style="display: block; margin: auto;" /> ] -- .pull-right[ ```r plot(inhabitants_cologne) ``` <img src="data:image/png;base64,#2_Data_Processing_Linking_files/figure-html/plot-inhabitants-1.png" style="display: block; margin: auto;" /> ] --- ## Combining Raster Layers to Calculate New Values: Immigrant Rates ```r immigrant_rate <- immigrants_cologne * 100 / inhabitants_cologne names(immigrant_rate) <- "immigrant_rate" immigrant_rate ``` ``` ## stars object with 2 dimensions and 1 attribute ## attribute(s): ## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's ## immigrant_rate 0.6637168 7.5 12.32877 16.48389 21.05263 100 62429 ## dimension(s): ## from to offset delta refsys point values x/y ## x 1 264 4094850 100 ETRS89-extended / LAEA Eu... FALSE NULL [x] ## y 1 289 3112950 -100 ETRS89-extended / LAEA Eu... FALSE NULL [y] ``` --- ## Result of Combination .pull-left[ ```r plot(immigrant_rate) ``` ] -- .pull-right[ <img src="data:image/png;base64,#2_Data_Processing_Linking_files/figure-html/combined-raster-plot-exec-1.png" style="display: block; margin: auto;" /> ] --- ## Raster Extraction To extract the raster values at a specific point by location we can use: ```r extracted_immigrants <- aggregate( x = immigrants_cologne %>% stars::st_transform_proj(4326), by = corona_cologne, FUN = mean, na.rm = TRUE ) extracted_immigrants ``` ``` ## stars object with 1 dimensions and 1 attribute ## attribute(s): ## Min. 1st Qu. Median Mean 3rd Qu. Max. ## immigrants_cologne.tif 3.909091 8.572482 12.46615 17.73039 23.70045 69.04762 ## dimension(s): ## from to offset delta refsys point ## geometry 1 86 NA NA WGS 84 FALSE ## values ## geometry MULTIPOLYGON (((6.971316 50...,...,MULTIPOLYGON (((7.064916 50... ``` --- ## Add Results to Existing Dataset This information can simply be added to an existing dataset: .pull-left[ ```r corona_cologne <- corona_cologne %>% dplyr::mutate( immigrants = extracted_immigrants[[1]] ) plot( corona_cologne[ c( "incidence", "immigrants", "hospitals_count" ) ] ) ``` ] -- .pull-right[ <img src="data:image/png;base64,#2_Data_Processing_Linking_files/figure-html/add-results-exec-1.png" style="display: block; margin: auto;" /> ] --- ## What's Next, What's Missing? .pull-left[ When working with geospatial data, researcher's degree of freedom is huge - we can vary the scale and zonation - permutation is crazy - do it theory-driven! ] .pull-right[ <img src="data:image/png;base64,#C:\Users\mueller2\talks_presentations\esra-workshop-first-steps-R-GIS\content\img\fig_linking_buffer_sealing.png" width="75%" style="display: block; margin: auto;" /> ] -- The analysis of geospatial data can get quite sophisticated - in the end, it's about modeling and testing spatial dependence - you may want to have a look at [Tobias Rüttenauer's fantastic materials from his recent workshop](https://ruettenauer.github.io/SICSS-Spatial/SICSS-spatial_part2.html) to get a first idea --- class: middle <br> <img src="data:image/png;base64,#C:\Users\mueller2\talks_presentations\esra-workshop-first-steps-R-GIS\content\img\r_gis_meme.jpg" width="50%" style="display: block; margin: auto;" /> --- class: middle ## Exercise 2: Geospatial Data Wrangling [Exercise](https://stefanjuenger.github.io/esra-workshop-first-steps-R-GIS/exercises/2_Geospatial_Data_Wrangling.html) [Solution](https://stefanjuenger.github.io/esra-workshop-first-steps-R-GIS/solutions/2_Geospatial_Data_Wrangling.html) --- layout: false class: center background-image: url(data:image/png;base64,#../assets/img/the_end.png) background-size: cover .left-column[ </br> <img src="data:image/png;base64,#C:\Users\mueller2\talks_presentations\esra-workshop-first-steps-R-GIS\content\img\stefan.png" width="90%" style="display: block; margin: auto;" /> ] .right-column[ .left[.small[<svg viewBox="0 0 512 512" style="height:1em;position:relative;display:inline-block;top:.1em;" xmlns="http://www.w3.org/2000/svg"> <path d="M464 64H48C21.49 64 0 85.49 0 112v288c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49 48-48V112c0-26.51-21.49-48-48-48zm0 48v40.805c-22.422 18.259-58.168 46.651-134.587 106.49-16.841 13.247-50.201 45.072-73.413 44.701-23.208.375-56.579-31.459-73.413-44.701C106.18 199.465 70.425 171.067 48 152.805V112h416zM48 400V214.398c22.914 18.251 55.409 43.862 104.938 82.646 21.857 17.205 60.134 55.186 103.062 54.955 42.717.231 80.509-37.199 103.053-54.947 49.528-38.783 82.032-64.401 104.947-82.653V400H48z"></path></svg> [`stefan.juenger@gesis.org`](mailto:stefan.juenger@gesis.org)] </br> .small[<svg viewBox="0 0 512 512" style="height:1em;position:relative;display:inline-block;top:.1em;" xmlns="http://www.w3.org/2000/svg"> <path d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"></path></svg> [`@StefanJuenger`](https://twitter.com/StefanJuenger)] </br> .small[<svg viewBox="0 0 496 512" style="height:1em;position:relative;display:inline-block;top:.1em;" xmlns="http://www.w3.org/2000/svg"> <path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path></svg> [`StefanJuenger`](https://github.com/StefanJuenger)] </br> .small[<svg viewBox="0 0 576 512" style="height:1em;position:relative;display:inline-block;top:.1em;" xmlns="http://www.w3.org/2000/svg"> <path d="M280.37 148.26L96 300.11V464a16 16 0 0 0 16 16l112.06-.29a16 16 0 0 0 15.92-16V368a16 16 0 0 1 16-16h64a16 16 0 0 1 16 16v95.64a16 16 0 0 0 16 16.05L464 480a16 16 0 0 0 16-16V300L295.67 148.26a12.19 12.19 0 0 0-15.3 0zM571.6 251.47L488 182.56V44.05a12 12 0 0 0-12-12h-56a12 12 0 0 0-12 12v72.61L318.47 43a48 48 0 0 0-61 0L4.34 251.47a12 12 0 0 0-1.6 16.9l25.5 31A12 12 0 0 0 45.15 301l235.22-193.74a12.19 12.19 0 0 1 15.3 0L530.9 301a12 12 0 0 0 16.9-1.6l25.5-31a12 12 0 0 0-1.7-16.93z"></path></svg> [`https://stefanjuenger.github.io`](https://stefanjuenger.github.io)]] </br> ]