LWIMW 9

I just finished my submission for Look What I Made Weekend 9. Look What I Made Weekend (LWIMW) is a chance for people to create something over the course of 48 hours. The concept is based on Ludum Dare and other game jams, but for LWIMW you don't have to make a game. Instead, you are free to pursue any creative endeavor and show off your results at the end.

NB: The content below is mostly a reprint of my submission at LWIMW.

This is my first app using Swift and also my first app using MapKit.

It's a simple app that takes an average of several GPS coordinates so that you can get a more accurate estimate of the coordinates of a point.

Main VC

Before the weekend, all I had done was a quick sketch of what the app might look like.

What I got done this weekend:

  • GPS tracking (auto mode)
  • Averaging
  • Displaying tracking and averaging on map
  • App icon
  • Sharing

Averaged Coordinates

Saved Coordinates

What still needs to be done:

  • Persistent storing of data
  • Manual mode
  • Multiple coordinate formats
  • Some testing and miscellaneous improvements

You can view the code or download the app on Github.

Tagged , , , , ,

Reality TV Show Name Generator App

The Idea

I had come across webpages like They Fight Crime and the Fantasy Name Generator, so creating a random reality TV show name generator app was not at all an original idea.

When I was flipping through channels one day and saw an endless stream of a naming convention, inspiration struck. I noticed that most reality shows have a name that's just an adjective and a noun: American Idol, Hell's Kitchen, Pawn Stars, Top Chef, just to name a few. This seemed like a really simple concept to implement while learning a new language. Really, it's the simplest unique app I've thought of.

The Javascript Implementation

In 2013, I created a Javascript version of the app. I was just learning Javascript when I thought of the idea, and it was an easy first project to complete.

Javascript version

The Objective-C Implementation

When I started learning Objective-C in December, I followed the Crystal Ball app tutorial on Team Treehouse, and after that, I had enough knowledge to build the most simple version of the Reality TV app.

Basically, all the simplest version needs to do is:

  • Have two arrays
  • Pick a random item from the first array
  • Pick a random item from the second array
  • Concatenate them
  • Display the name
  • Have a straightforward way to refresh and get a new random name

Here's a screenshot of an early version:

Early Objective-C version

Components

As I advanced in Objective-C, I wanted to add more features. The first thing I added was a "share" button. This was surprisingly simple.

Once I started taking the Mobile Engineering class at The Iron Yard, I began to add more features. I wanted to add a "recent names" list and a "favorites" list and to have a way for people to add a name to their favorites.

Once we learned about TableViewControllers, adding the recent names list was fairly straightforward.

Early Recent Items

Early Share & Recent

What was more complicated was adding a "favorites" list. I wanted to have the favorites persist between app uses. This is something we haven't covered yet in class, but I got a tip from John–one of our TAs–and I managed to figure out how to implement it using NSUserDefaults.

Now that all of the functionality worked, what was left was the design. I needed icons for favoriting and sharing, and I wanted to make a custom TV design instead of using one someone else made since it was such a central part of the app.

Design

I wasn't happy with the way the original design looked, so I changed the color palette.

#009762#13BD81#000000#ffffff

Looking better!

One thing I'd had no intention of making was a sliding drawer that would give you more options. I didn't really even think of this as being an option! But then in class we made a drawer for one of our apps, and I realized it would be the perfect way to handle all the buttons I wanted to have (favorite, favorites, recent, share). I designed a drawer and added it to the app.

Drawer

For the icons in the drawer (favorite, menu, and share) and the refresh icon, I used Font Awesome and Ionicons, which are both free-to-use icon packages.

For the TV image, I wanted to create something custom, since it was so prominently displayed in the app. I played around some in Photoshop. Even though I can't draw at all, I can sure use a rectangle tool, and I think I managed to come up with something that looks acceptable. You can see my design on Dribbble. I also used this TV as my app icon, but I gave the app version wider antennae to make them easier to see in the smaller size.

I've been learning Sketch, but I've really only used it for UI mockups, not drawing things. Hopefully soon I'll be able to use a more appropriate tool like Sketch or Illustrator instead of using Photoshop for this kind of work, but for the moment, I'm still more comfortable in Photoshop.

Custom TV

The last design decision I had to make was how to handle:

  1. Favoriting recent names
  2. Sharing recent names and favorites

Originally, I had a share icon on the side of each cell on the recent items list. But once I added favorites, I would have needed to have two icons in each cell, one for sharing and one for favoriting. I decided this would be too cluttered, so I decided to return users to the main view when they tap on a name in a list. From there, they can do anything they would have been able to do when the name first appeared. This setup has the additional benefit of allowing people to make screenshots of items from their recent items list or favorites list.

Complete

Once the app is available on the store, you'll be able to download it here. The code is available on Github.

Tagged ,

NSUserDefaults Example with NSMutableArray

I was misinformed that to have information persist between app uses, you had to either use Core Data or a cloud backend. Boy, was I ever wrong. It's so much easier than that!

From NSHipster:

NSHipster's opinion

In this case, we're going to be doing something even easier than the full NSKeyedArchiver. For this blog post, we'll be creating a very simple array example for using NSUserDefaults to persistently store data. We'll be able to add items to and remove items from the array.

  • Start a new Single View Application project.
  • Delete ViewController.h and ViewController.m.
  • Remove the View Controller from the storyboard.
  • Make a new file that is a subclass of UITableViewController. Name it ItemTVC.
  • Drag a Table View Controller into the storyboard and make it the Initial View Controller (in the Attributes inspector). Give it the class ItemTVC. Give the cell the identifier "Cell".
  • Leave ItemTVC.h as it is and work in ItemTVC.m.

In viewDidLoad:

- (void)viewDidLoad {
    [super viewDidLoad];

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

    self.array = [[defaults arrayForKey:@"array"] mutableCopy];

}

NSUserDefaults will always return an immutable version of any object you pass it. As a result, we need to use mutableCopy before using the array we've retrieved from NSUserDefaults.

In (IBAction)addItem (storyboard for add item button) or insertNewObject (code for add item button) or similar:

// if you aren't using storyboard, this might be insertNewObject or a similar method instead of an IBAction
- (IBAction)addItem:(id)sender {

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

    if ([self.array count] == 0) {
        self.array = [[NSMutableArray alloc] init];
    }

    [self.array addObject:[NSDate date]];
    [defaults setObject:self.array forKey:@"array"];
    [self.tableView reloadData];

}

In cellForRowAtIndexPath:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];

    NSDate *item = self.array[indexPath.row];
    cell.textLabel.text = [item description];

    return cell;
}

This step is optional. If you want to be able to remove items, add this in commitEditingStyle forRowAtIndexPath.

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {

    if (editingStyle == UITableViewCellEditingStyleDelete) {

        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

        [self.array removeObjectAtIndex:indexPath.row];
        [defaults setObject:self.array forKey:@"array"];
        [self.tableView reloadData];

    }

}

The full code is available on Github.

References

Tagged , , , , , , ,

First Post with Pelican

Let's see if it works!

Tagged ,

Book Review: Leaflet.js Essentials by Paul Crickard III

This is a review of Leaflet.js Essentials by Paul Crickard III.

Contents

  • Chapter 1 introduces creating a basic map from scratch with leaflet, how to use map tiles, how to create popups, and how to enable geolocation.
  • Chapter 2 shows how to create GeoJSON data, how to style GeoJSON layers by type (LineString, Polygon, etc.), and how to filter to display only a subset of the data.
  • Chapter 3 introduces the leaflet.heat plugin for density heat maps and the heatmap.js library for intensity heat maps. It also teaches how to style and animate heat maps, how to create choropleths, and how to use buttons outside the map to change what's shown in the map.
  • Chapter 4 introduces how to create custom markers and marker shadows as well as several markers that are available online. We also learn how to cluster markers, animate markers, and create pie or bar chart markers.
  • Chapter 5 shows how to use many ESRI resources in Leaflet, including map tiles, shapefiles, and geocoding.
  • Chapter 6 introduces using Node.JS, Python, and C# with leaflet, including getting data via AJAX calls, using MongoDB to save created map points, and creating desktop applications with C#.

Review

This is a very well laid-out, detailed guide to Leaflet. It is intended for complete newbies to Leaflet, but it provides some very advanced information as well. The book claims you should have some JavaScript knowledge before beginning, but I'm not entirely sure this is necessary, since Leaflet maps rely on only very minimal JavaScript.

I enjoyed the breadth of this book. I think the book does a great job of covering the information a beginner would need. It also provides information on a wide variety of topics like good tips for designing for mobile, a very comprehensive list of map tiles (many of these I did not know about), and information on several JavaScript libraries and Leaflet plug-ins that allow for additional functionality within the maps.

On the other hand, there is one major criticism I have with this book, especially for a beginner audience. I'm not that happy with the included code samples or how they are organized. This may just be personal preference, but I prefer books that have the "before this section" code and the "after this section" code so you can start with the before and then double-check with the after if you made an error or didn't understand something. I feel like the provided code samples made it difficult to follow along with programming while reading.

All in all, I wouldn't hesitate to recommend this book to someone beginning to learn Leaflet. For more advanced Leaflet users, there are some gems that may or may not make the book a worthwhile purchase. I will say that I use Leaflet almost every day, and I did learn several things from reading Leaflet.js Essentials.

4 out of 5 stars

Tagged ,

Add Icons to Layer Control in Leaflet

When you have multiple layers, it can help to add a legend to the map. This tutorial shows how to add icons to the layer control in Leaflet. This tutorial shows only marker layers, but you could create icons for polygon layers and use those as well.

Legend with marker icons

The following code sets up our map, layers, and the layer control. This is the standard way maps look in Leaflet, with no visual indication of which layer goes with which markers, without checking the boxes on and off.

var map = L.map('map', {
    center: [33.8, -84.4],
    zoom: 11,
        layers: [trainLayer, treeLayer]
});

var overlayMaps = {
    "Train": trainLayer,
    "Tree": treeLayer
};

L.control.layers(null, overlayMaps, {
    collapsed: false
}).addTo(map);

Default legend

In order to add a legend to the layer control, you can simply change the overlayMaps code to add any HTML you'd like. In this case, we'll add the map icon pngs with a small height so they will look okay in the layer control:

var overlayMaps = {
    "<img src='http://mollietaylor.com/skills/js/leaflet/train.png' height=24>Train": trainLayer,
    "<img src='http://mollietaylor.com/skills/js/leaflet/arbol.png' height=24>Tree": treeLayer
};

It's really that simple! I searched the documentation and the web, and I couldn't figure out how to do this, so I figured I'd just try adding the HTML. Turns out it looks great!

Full code available on CodePen. Map icons by Nicolas Mollet and Axel Rodriguez.

Tagged , , ,

Changing Leaflet Slidemapper to a Side-by-Side View

The default setup in slidemapper is to have the slides either above or below the map. However, it is also possible to place the slides to one side of the map.

Slides beside map

CSS changes

We need to make a couple of changes to how the smapp-show and smapp-map classes are displayed. You'll want to play around with these some, but this is what worked well for me:

.smapp-show { 
    width:50%; 
}
.smapp-show .slide { 
    overflow-y:visible; 
}
.smapp-map { 
    margin:10px; 
    width:48%; 
    height:95%; 
    position:absolute; 
    top:0; 
    right:0; 
}

Slidemapper changes

On the slidemapper side of things, the only edit we need to make is changing the options of our slideshow. Specifically, we need to edit the slideHeight and mapHeight to something appropriate for having the slides beside the map. For example, I chose:

$mySlideMap = $('#slideshow-container').slideMapper({
    slideHeight: 540,
    mapHeight: '98%'
});

And that's all the changes you need to make to have a side-by-side slideshow in slidemapper! The full code is available as a gist.

References

Tagged , , , ,

Link to Another Slide in Leaflet Slidemapper

The slidemapper plugin for Leaflet is very useful, but when building large slideshows, it can be annoying that there is no easy way to link to specific slides. For example, you might want to create a table of contents or link from the last slide back to the first. Fortunately, there's a way to move to specific slides, instead of just advancing or moving back by 1. We'll use jQuery to do this. We already have jQuery loaded for slidemapper.

Specify Where Links Will Go

After the $mySlideMap code in your html file, you can create a list of links that you will later add to the slides. Generally, this will be the last thing in the <script> tag at the end of the body.

For example, in the sample code from the slidemapper example, the <script> initially contains:

$mySlideMap = $('#slideshow-container')
.slideMapper();

$mySlideMap.slideMapper('add', EXAMPLEDATA);

At the end of the script, let's add a link to the first slide and a link to the last slide. The jQuery we use allows any code with a specific class to be transformed into a link. We can combine that with the slidemapper move method to create links to specific slides.

$('.toc').click(function() {
    $mySlideMap.slideMapper('move', 0, true);
});

$('.end').click(function() {
    $mySlideMap.slideMapper('move', EXAMPLEDATA.length - 1, true);
});

Add the Links

Next, we need to actually include the links in the slides. The links references will go in the data file. In the example, the file is named data.js.

Here's a link to the last slide from the first slide:

// intro marker
{
    icon: 'other.png',
    marker: [42.516846, -70.898499],
    center: [40.423, -98.7372],
    html: '<table style="margin:0 40px; padding:10px"><tr>' +
            '<td><img src="http://placehold.it/300x180&text=Map+Stuff"/></td>' +
            '<td style="padding-left:10px">' +
                '<h1>SlideMapper FTW!</h1>' +
                '<p>This is a demo of the different sorts of slides you can setup using slidemapper.</p>' +
                '<p><a class="end">Skip to the end.</a></p>' +
            '</td>' +
        '</tr></table>',
    popup: 'So it begins!'
},

And here's a link from the last slide to the first slide:

// empty slide
{
    html: '<div style="margin:0 40px; padding:20px 10px">' +
            '<div>' +
                '<h2>The End</h2>' +
                '<p>Goodbye.</p>' +
                '<p><a class="toc">Return to Table of Contents.</a></p>' +
            '</div>' +
        '</div>'
}

Prettifying

Because of the unusual way these links are made using jQuery, we should probably add some css to make them look like links. I added the following css to the <head>, but you can add any css to the head or as a separate stylesheet.

<style type="text/css">
    a {
        color: orange;
        cursor: pointer;
        text-decoration: underline;
    }
</style>

Here's an example of a link from one slide to another

And now you have an easy way to navigate through a long slidemapper deck. You could even create a table of contents at the beginning and then link back to that from each slide.

The full code is available in a gist.

References

Tagged , , , , , , ,

Center Map on Layer Change in Leaflet

In Leaflet, it can be helpful to change the bounds of the map when the user adds or changes the visible map layers.

The Basics

First, we'll start with the initial code including our map and polygon layers:

var circle = L.circle([51.508, -0.11], 500, {
    color: 'red',
    fillColor: '#f03',
    fillOpacity: 0.5
});

var polygon = L.polygon([
    [51.509, -0.08],
    [51.503, -0.06],
    [51.51, -0.047]
]);

var map = L.map('map', {
    center: [51.505, -0.09],
    zoom: 13
});

var overlayMaps = {
    "Circle": circle,
    "Polygon": polygon
};

L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '&copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'
}).addTo(map);

L.control.layers(overlayMaps, null, {
    collapsed: false
}).addTo(map);

So far, most of this should be familiar from the Leaflet Quick Start Guide.

Next, we want to add a listener function that will zoom and re-center upon a change in the circle or polygon layer.

The .on method of map allows you to watch for an event to occur and then execute a function when it does. In this case, we want to wait for the event 'baselayerchange'. This way the map will automatically zoom and recenter when the user changes layers.

map.on('baselayerchange', function(e) {
    console.log(e);
    map.fitBounds(e.layer);
});

The map automatically zooms to the bounds of the shape when the layer is activated.

Options

There are a few options for exactly how the map is zoomed and/or re-centered and for what type of layers are affected.

If you want to do this with overlay layers instead of base layers, you can substitute 'overlayadd' for 'baselayerchange'. Using overlay layers is more common for drawing shapes, but treating your layers as base layers makes it easy to display only one at a time.

map.on('overlayadd', function(e) {
    console.log(e);
    map.fitBounds(e.layer);
});

fitBounds automatically zooms to the tightest zoom level where the whole shape is visible. If you don't want to use fitBounds (say you're centering on a new overlay layer and don't want to zoom all the way in), you can use setView or panTo instead. panTo animates as the view changes.

map.on('overlayadd', function(e) {
    console.log(e);
    map.panTo(e.layer);
});

And there you have a few different ways to center and/or zoom in when an overlay layer is added or when the baselayer is changed. I'd recommend you check out the documentation for fitBounds, setView, and panTo and play around with the options. The options for L.control.layers are also helpful. For example, you can set collapsed to false to encourage users to change the layers.

The full code is available in a gist.

References

Tagged , , , , , , ,

Merge by City and State in R

Often, you'll need to merge two data frames based on multiple variables. For this example, we'll use the common case of needing to merge by city and state.

First, you need to read in both your data sets:

# import city coordinate data:
coords <- read.csv("cities-coords.csv",
    header = TRUE,
    sep = ",")

# import population data:
data <- read.csv("cities-data.csv",
    header = TRUE,
    sep = ",")

Next comes the merge. You can use by.x and by.y to declare which variables the merge will be based on. If the variables have exactly the same name in both data sets, you can use by instead of by.x and by.y.

x and y represent the two data sets you are merging, in that order.

You also want to state whether you want to include all data from either data set, using all or all.x and all.y. In this case, we want to make sure we hold onto all our city data, even data for the cities we do not have coordinates for.

# merge data & coords by city & state:
dataCoords <- merge(coords, data, 
    by.x = c("City", "State"),
    by.y = c("city", "state"),
    all.x = FALSE,
    all.y = TRUE)

Running that code shows what we would expect. Houston is included in the final data set even though there are no coordinates for it, while Dallas is not included since it has coordinates but no data:

            City State Latitude  Longitude year population
1        Chicago    IL 41.85003  -87.65005 2012    2714856
2       Columbus    GA 32.46098  -84.98771 2012     198413
3       Columbus    OH 39.96118  -82.99879 2012     809798
4       Columbus    OH 39.96118  -82.99879 2010     787033
5    Los Angeles    CA 34.05223 -118.24368 2012    3857799
6       New York    NY 40.71427  -74.00597 2012    8336697
7       New York    NY 40.71427  -74.00597 2010    8175133
8  San Francisco    CA 37.77823 -122.44250 2012     825863
9  San Francisco    CA 37.77823 -122.44250 2010     805235
10       Houston    TX       NA         NA 2012    2160821

Bonus

If you'd like to get a list of which cases got merged in but lack coordinate data, there's a simple line of code to do that:

> dataCoords[!complete.cases(dataCoords[,c(3,4)]),]
      City State Latitude Longitude year population
10 Houston    TX       NA        NA 2012    2160821

Also, you might want to tidy up the names of your variables, if they followed different conventions in their respective initial data sets:

> names(dataCoords) <- c("City", "State", "Latitude", "Longitude", "Year", "Population")
> dataCoords
            City State Latitude  Longitude Year Population
1        Chicago    IL 41.85003  -87.65005 2012    2714856
2       Columbus    GA 32.46098  -84.98771 2012     198413
3       Columbus    OH 39.96118  -82.99879 2012     809798
4       Columbus    OH 39.96118  -82.99879 2010     787033
5    Los Angeles    CA 34.05223 -118.24368 2012    3857799
6       New York    NY 40.71427  -74.00597 2012    8336697
7       New York    NY 40.71427  -74.00597 2010    8175133
8  San Francisco    CA 37.77823 -122.44250 2012     825863
9  San Francisco    CA 37.77823 -122.44250 2010     805235
10       Houston    TX       NA         NA 2012    2160821

The full sample code is available as a gist.

References

Tagged , ,