Proximity filter without Solr
In order to solve this issue, I combined the results of two views.


The Primary View 


uses the search index and displays indexed nodes
has the correct search facet exposed filters
can't perform a proximity search (without Solr &amp; Search API Location)

The Secondary View


uses the node content and displays non-indexed nodes
can't use search facets for the exposed filters because the nodes are not indexed
can perform a proximity search with just Geofield, GeoPHP, and GeoCoder (all of which we already had installed)



To create the secondary view, I followed the steps outlined here.

Then I used hook_search_api_views_query_alter() to alter the query for the primary view. In this hook, I retrieve the secondary view, pass in the zip code from the exposed filter in the primary view, and execute the secondary view to get the results. Then I turn those results into an array of nids.

Finally, I add a condition to the views query to filter out all nodes that do not have an nid from that array.

/**
 * Implements hook_search_api_views_query_alter().
 */
function mymodule_search_api_views_query_alter(view &amp;$view, SearchApiViewsQuery &amp;$query) {
  if ($view-&gt;name === 'my_primary_view') {

    // Try to get the zip code from the exposed filter.
    $zip_code = NULL;
    if (isset($view-&gt;exposed_input['field_geo_latlon'])) {
      $zip_code = $view-&gt;exposed_input['field_geo_latlon'];
      unset($view-&gt;exposed_raw_input['field_geo_latlon']);
      unset($view-&gt;exposed_input['field_geo_latlon']);
      unset($view-&gt;exposed_data['field_geo_latlon']);
    }

    // Get the second view, pass in the zip code, get the results.
    $proximity_view = views_get_view('my_secondary_view');
    if (isset($zip_code)) {
      $proximity_view-&gt;exposed_input['field_geofield_distance'] = array(
        'distance' =&gt; '100',
        'unit' =&gt; '3959',
        'origin' =&gt; $zip_code,
      );
    }
    $proximity_view-&gt;init_display();
    $proximity_view-&gt;pre_execute();
    $proximity_view-&gt;execute();

    // Turn the results into an array of nids.
    $proximity_nids = array();
    foreach ($proximity_view-&gt;result as $proximity_result) {
      $proximity_nids[] = $proximity_result-&gt;nid;
    }

    // Add that condition to the view query.
    $view-&gt;query-&gt;condition('nid', $proximity_nids, 'IN');
  }
}


Note: Initially, the dysfunctional zip code filter from the primary view was filtering out all results when a zip code was entered by a user. I needed to keep this field so that users could still enter their zip code, but I did not want the filter to actually be applied.

To get around this, I edited the settings on the exposed zip code filter in the primary view so that when a zip code is provided, it returns all nodes where the zip code is not empty.

This ensures that entering a zip code into the primary view won't change the result set (technically it will remove results without zip codes, but that's ok) but will still allow us to retrieve the value from the input and pass it to the secondary view for the "real" proximity filter.