/*
* Copyright (C) 2012-2013 Paul Watts (paulcwatts@gmail.com)
* and individual contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onebusaway.android.util;
import org.onebusaway.android.BuildConfig;
import org.onebusaway.android.R;
import org.onebusaway.android.app.Application;
import org.onebusaway.android.io.elements.ObaRegion;
import org.onebusaway.android.io.elements.ObaRegionElement;
import org.onebusaway.android.io.request.ObaRegionsRequest;
import org.onebusaway.android.io.request.ObaRegionsResponse;
import org.onebusaway.android.provider.ObaContract;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.location.Location;
import android.net.Uri;
import android.util.Log;
import java.security.MessageDigest;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
/**
* A class containing utility methods related to handling multiple regions in OneBusAway
*/
public class RegionUtils {
private static final String TAG = "RegionUtils";
public static final double METERS_TO_MILES = 0.000621371;
private static final int DISTANCE_LIMITER = 100; // miles
/**
* Get the closest region from a list of regions and a given location
*
* This method also enforces the constraints in isRegionUsable() to
* ensure the returned region is actually usable by the app
*
* @param regions list of regions
* @param loc location
* @param enforceThreshold true if the DISTANCE_LIMITER threshold should be enforced, false if
* it should not
* @return the closest region to the given location from the list of regions, or null if a
* enforceThreshold is true and the closest region exceeded DISTANCE_LIMITER threshold or a
* region couldn't be found
*/
public static ObaRegion getClosestRegion(ArrayList<ObaRegion> regions, Location loc,
boolean enforceThreshold) {
if (loc == null) {
return null;
}
float minDist = Float.MAX_VALUE;
ObaRegion closestRegion = null;
Float distToRegion;
NumberFormat fmt = NumberFormat.getInstance();
if (fmt instanceof DecimalFormat) {
((DecimalFormat) fmt).setMaximumFractionDigits(1);
}
double miles;
Log.d(TAG, "Finding region closest to " + loc.getLatitude() + "," + loc.getLongitude());
for (ObaRegion region : regions) {
if (!isRegionUsable(region)) {
Log.d(TAG,
"Excluding '" + region.getName() + "' from 'closest region' consideration");
continue;
}
distToRegion = getDistanceAway(region, loc.getLatitude(), loc.getLongitude());
if (distToRegion == null) {
Log.e(TAG, "Couldn't measure distance to region '" + region.getName() + "'");
continue;
}
miles = distToRegion * METERS_TO_MILES;
Log.d(TAG, "Region '" + region.getName() + "' is " + fmt.format(miles) + " miles away");
if (distToRegion < minDist) {
closestRegion = region;
minDist = distToRegion;
}
}
if (enforceThreshold) {
if (minDist * METERS_TO_MILES < DISTANCE_LIMITER) {
return closestRegion;
} else {
return null;
}
}
return closestRegion;
}
/**
* Get the region name if it is available. If there is a custom url instead of a region from
* the region api, then hash the custom url and return it.
*
* @return regionName
*/
public static String getObaRegionName() {
String regionName = null;
ObaRegion region = Application.get().getCurrentRegion();
if (region != null && region.getName() != null) {
regionName = region.getName();
} else if (Application.get().getCustomApiUrl() != null) {
regionName = createHashCode(Application.get().getCustomApiUrl().getBytes());
}
return regionName;
}
private static String createHashCode(byte[] bytes) {
MessageDigest digest;
try {
digest = MessageDigest.getInstance("SHA-1");
digest.update(bytes);
return Application.get().getString(R.string.analytics_label_custom_url) +
": " + Application.getHex(digest.digest());
} catch (Exception e) {
return Application.get().getString(R.string.analytics_label_custom_url);
}
}
/**
* Returns the distance from the specified location
* to the center of the closest bound in this region.
*
* @return distance from the specified location to the center of the closest bound in this
* region, in meters
*/
public static Float getDistanceAway(ObaRegion region, double lat, double lon) {
ObaRegion.Bounds[] bounds = region.getBounds();
if (bounds == null) {
return null;
}
float[] results = new float[1];
float minDistance = Float.MAX_VALUE;
for (ObaRegion.Bounds bound : bounds) {
Location.distanceBetween(lat, lon, bound.getLat(), bound.getLon(), results);
if (results[0] < minDistance) {
minDistance = results[0];
}
}
return minDistance;
}
public static Float getDistanceAway(ObaRegion region, Location loc) {
return getDistanceAway(region, loc.getLatitude(), loc.getLongitude());
}
/**
* Returns the center and lat/lon span for the entire region.
*
* @param results Array to receive results.
* results[0] == latSpan of region
* results[1] == lonSpan of region
* results[2] == lat center of region
* results[3] == lon center of region
*/
public static void getRegionSpan(ObaRegion region, double[] results) {
if (results.length < 4) {
throw new IllegalArgumentException("Results array is < 4");
}
if (region == null) {
throw new IllegalArgumentException("Region is null");
}
double latMin = 90;
double latMax = -90;
double lonMin = 180;
double lonMax = -180;
// This is fairly simplistic
for (ObaRegion.Bounds bound : region.getBounds()) {
// Get the top bound
double lat = bound.getLat();
double latSpanHalf = bound.getLatSpan() / 2.0;
double lat1 = lat - latSpanHalf;
double lat2 = lat + latSpanHalf;
if (lat1 < latMin) {
latMin = lat1;
}
if (lat2 > latMax) {
latMax = lat2;
}
double lon = bound.getLon();
double lonSpanHalf = bound.getLonSpan() / 2.0;
double lon1 = lon - lonSpanHalf;
double lon2 = lon + lonSpanHalf;
if (lon1 < lonMin) {
lonMin = lon1;
}
if (lon2 > lonMax) {
lonMax = lon2;
}
}
results[0] = latMax - latMin;
results[1] = lonMax - lonMin;
results[2] = latMin + ((latMax - latMin) / 2.0);
results[3] = lonMin + ((lonMax - lonMin) / 2.0);
}
/**
* Determines if the provided location is within the provided region span
*
* Note: This does not handle cases when the region span crosses the
* International Date Line properly
*
* @param location that will be compared to the provided regionSpan
* @param regionSpan span information for the region
* regionSpan[0] == latSpan of region
* regionSpan[1] == lonSpan of region
* regionSpan[2] == lat center of region
* regionSpan[3] == lon center of region
* @return true if the location is within the region span, false if it is not
*/
public static boolean isLocationWithinRegion(Location location, double[] regionSpan) {
if (regionSpan == null || regionSpan.length < 4) {
throw new IllegalArgumentException("regionSpan is null or has length < 4");
}
if (location == null || location.getLongitude() > 180.0 || location.getLongitude() < -180.0
||
location.getLatitude() > 90 || location.getLatitude() < -90) {
throw new IllegalArgumentException("Location must be a valid location");
}
double minLat = regionSpan[2] - (regionSpan[0] / 2);
double minLon = regionSpan[3] - (regionSpan[1] / 2);
double maxLat = regionSpan[2] + (regionSpan[0] / 2);
double maxLon = regionSpan[3] + (regionSpan[1] / 2);
return minLat <= location.getLatitude() && location.getLatitude() <= maxLat
&& minLon <= location.getLongitude() && location.getLongitude() <= maxLon;
}
/**
* Determines if the provided location is within the provided region
*
* Note: This does not handle cases when the region span crosses the
* International Date Line properly
*
* @param location that will be compared to the provided region
* @param region provided region
* @return true if the location is within the region, false if it is not
*/
public static boolean isLocationWithinRegion(Location location, ObaRegion region) {
double[] regionSpan = new double[4];
getRegionSpan(region, regionSpan);
return isLocationWithinRegion(location, regionSpan);
}
/**
* Checks if the given region is usable by the app, based on what this app supports
* - Is the region active?
* - Does the region support the OBA Discovery APIs?
* - Does the region support the OBA Realtime APIs?
* - Is the region experimental, and if so, did the user opt-in via preferences?
*
* @param region region to be checked
* @return true if the region is usable by this application, false if it is not
*/
public static boolean isRegionUsable(ObaRegion region) {
if (!region.getActive()) {
Log.d(TAG, "Region '" + region.getName() + "' is not active.");
return false;
}
if (!region.getSupportsObaDiscoveryApis()) {
Log.d(TAG, "Region '" + region.getName() + "' does not support OBA Discovery APIs.");
return false;
}
if (!region.getSupportsObaRealtimeApis()) {
Log.d(TAG, "Region '" + region.getName() + "' does not support OBA Realtime APIs.");
return false;
}
if (region.getExperimental() && !Application.getPrefs().getBoolean(
Application.get().getString(R.string.preference_key_experimental_regions), false)) {
Log.d(TAG,
"Region '" + region.getName() + "' is experimental and user hasn't opted in.");
return false;
}
return true;
}
/**
* Format the OTP base URL so query parameters can be added safely.
*
* @param baseUrl OpenTripPlanner base URL from the Region
* @return OTP server URL with trailing slash trimmed.
*/
public static String formatOtpBaseUrl(String baseUrl) {
return baseUrl.replaceFirst("/$", "");
}
/**
* Gets regions from either the server, local provider, or if both fails the regions file
* packaged
* with the APK. Includes fail-over logic to prefer sources in above order, with server being
* the first preference.
*
* @param forceReload true if a reload from the server should be forced, false if it should not
* @return a list of regions from either the server, the local provider, or the packaged
* resource file
*/
public synchronized static ArrayList<ObaRegion> getRegions(Context context,
boolean forceReload) {
ArrayList<ObaRegion> results;
if (!forceReload) {
//
// Check the DB
//
results = RegionUtils.getRegionsFromProvider(context);
if (results != null) {
Log.d(TAG, "Retrieved regions from database.");
return results;
}
Log.d(TAG, "Regions list retrieved from database was null.");
}
results = RegionUtils.getRegionsFromServer(context);
if (results == null || results.isEmpty()) {
Log.d(TAG, "Regions list retrieved from server was null or empty.");
if (forceReload) {
//If we tried to force a reload from the server, then we haven't tried to reload from local provider yet
results = RegionUtils.getRegionsFromProvider(context);
if (results != null) {
Log.d(TAG, "Retrieved regions from database.");
return results;
} else {
Log.d(TAG, "Regions list retrieved from database was null.");
}
}
//If we reach this point, the call to the Regions REST API failed and no results were
//available locally from a prior server request.
//Fetch regions from local resource file as last resort (otherwise user can't use app)
results = RegionUtils.getRegionsFromResources(context);
if (results == null) {
//This is a complete failure to load region info from all sources, app will be useless
Log.d(TAG, "Regions list retrieved from local resource file was null.");
return results;
}
Log.d(TAG, "Retrieved regions from local resource file.");
} else {
Log.d(TAG, "Retrieved regions list from server.");
//Update local time for when the last region info was retrieved from the server
Application.get().setLastRegionUpdateDate(new Date().getTime());
}
//If the region info came from the server or local resource file, we need to save it to the local provider
RegionUtils.saveToProvider(context, results);
return results;
}
public static ArrayList<ObaRegion> getRegionsFromProvider(Context context) {
// Prefetch the bounds to limit the number of DB calls.
HashMap<Long, ArrayList<ObaRegionElement.Bounds>> allBounds = getBoundsFromProvider(
context);
HashMap<Long, ArrayList<ObaRegionElement.Open311Server>> allOpen311Servers =
getOpen311ServersFromProvider(context);
Cursor c = null;
try {
final String[] PROJECTION = {
ObaContract.Regions._ID,
ObaContract.Regions.NAME,
ObaContract.Regions.OBA_BASE_URL,
ObaContract.Regions.SIRI_BASE_URL,
ObaContract.Regions.LANGUAGE,
ObaContract.Regions.CONTACT_EMAIL,
ObaContract.Regions.SUPPORTS_OBA_DISCOVERY,
ObaContract.Regions.SUPPORTS_OBA_REALTIME,
ObaContract.Regions.SUPPORTS_SIRI_REALTIME,
ObaContract.Regions.TWITTER_URL,
ObaContract.Regions.EXPERIMENTAL,
ObaContract.Regions.STOP_INFO_URL,
ObaContract.Regions.OTP_BASE_URL,
ObaContract.Regions.OTP_CONTACT_EMAIL
};
ContentResolver cr = context.getContentResolver();
c = cr.query(
ObaContract.Regions.CONTENT_URI, PROJECTION, null, null,
ObaContract.Regions._ID);
if (c == null) {
return null;
}
if (c.getCount() == 0) {
c.close();
return null;
}
ArrayList<ObaRegion> results = new ArrayList<ObaRegion>();
c.moveToFirst();
do {
long id = c.getLong(0);
ArrayList<ObaRegionElement.Bounds> bounds = allBounds.get(id);
ObaRegionElement.Bounds[] bounds2 = (bounds != null) ?
bounds.toArray(new ObaRegionElement.Bounds[]{}) :
null;
ArrayList<ObaRegionElement.Open311Server> open311Servers = allOpen311Servers.get(id);
ObaRegionElement.Open311Server[] open311Servers2 = (open311Servers != null) ?
open311Servers.toArray(new ObaRegionElement.Open311Server[]{}) :
null;
results.add(new ObaRegionElement(id, // id
c.getString(1), // Name
true, // Active
c.getString(2), // OBA Base URL
c.getString(3), // SIRI Base URL
bounds2, // Bounds
open311Servers2, // Open311 servers
c.getString(4), // Lang
c.getString(5), // Contact Email
c.getInt(6) > 0, // Supports Oba Discovery
c.getInt(7) > 0, // Supports Oba Realtime
c.getInt(8) > 0, // Supports Siri Realtime
c.getString(9), // Twitter URL
c.getInt(10) > 0, // Experimental
c.getString(11), // StopInfoUrl
c.getString(12), // OTP Base URL
c.getString(13) // OTP Contact Email
));
} while (c.moveToNext());
return results;
} finally {
if (c != null) {
c.close();
}
}
}
private static HashMap<Long, ArrayList<ObaRegionElement.Bounds>> getBoundsFromProvider(
Context context) {
// Prefetch the bounds to limit the number of DB calls.
Cursor c = null;
try {
final String[] PROJECTION = {
ObaContract.RegionBounds.REGION_ID,
ObaContract.RegionBounds.LATITUDE,
ObaContract.RegionBounds.LONGITUDE,
ObaContract.RegionBounds.LAT_SPAN,
ObaContract.RegionBounds.LON_SPAN
};
HashMap<Long, ArrayList<ObaRegionElement.Bounds>> results
= new HashMap<Long, ArrayList<ObaRegionElement.Bounds>>();
ContentResolver cr = context.getContentResolver();
c = cr.query(ObaContract.RegionBounds.CONTENT_URI, PROJECTION, null, null, null);
if (c == null) {
return results;
}
if (c.getCount() == 0) {
c.close();
return results;
}
c.moveToFirst();
do {
long regionId = c.getLong(0);
ArrayList<ObaRegionElement.Bounds> bounds = results.get(regionId);
ObaRegionElement.Bounds b = new ObaRegionElement.Bounds(
c.getDouble(1),
c.getDouble(2),
c.getDouble(3),
c.getDouble(4));
if (bounds != null) {
bounds.add(b);
} else {
bounds = new ArrayList<ObaRegionElement.Bounds>();
bounds.add(b);
results.put(regionId, bounds);
}
} while (c.moveToNext());
return results;
} finally {
if (c != null) {
c.close();
}
}
}
private static HashMap<Long, ArrayList<ObaRegionElement.Open311Server>> getOpen311ServersFromProvider(
Context context) {
// Prefetch the bounds to limit the number of DB calls.
Cursor c = null;
try {
final String[] PROJECTION = {
ObaContract.RegionOpen311Servers.REGION_ID,
ObaContract.RegionOpen311Servers.JURISDICTION,
ObaContract.RegionOpen311Servers.API_KEY,
ObaContract.RegionOpen311Servers.BASE_URL
};
HashMap<Long, ArrayList<ObaRegionElement.Open311Server>> results
= new HashMap<Long, ArrayList<ObaRegionElement.Open311Server>>();
ContentResolver cr = context.getContentResolver();
c = cr.query(ObaContract.RegionOpen311Servers.CONTENT_URI, PROJECTION, null, null, null);
if (c == null) {
return results;
}
if (c.getCount() == 0) {
c.close();
return results;
}
c.moveToFirst();
do {
long regionId = c.getLong(0);
ArrayList<ObaRegionElement.Open311Server> open311Servers = results.get(regionId);
ObaRegionElement.Open311Server b = new ObaRegionElement.Open311Server(
c.getString(1),
c.getString(2),
c.getString(3));
if (open311Servers != null) {
open311Servers.add(b);
} else {
open311Servers = new ArrayList<ObaRegionElement.Open311Server>();
open311Servers.add(b);
results.put(regionId, open311Servers);
}
} while (c.moveToNext());
return results;
} finally {
if (c != null) {
c.close();
}
}
}
private synchronized static ArrayList<ObaRegion> getRegionsFromServer(Context context) {
ObaRegionsResponse response = ObaRegionsRequest.newRequest(context).call();
return new ArrayList<ObaRegion>(Arrays.asList(response.getRegions()));
}
/**
* Retrieves region information from a regions file bundled within the app APK
*
* IMPORTANT - this should be a last resort, and we should always try to pull regions
* info from the local provider or Regions REST API instead of from the bundled file.
*
* This method is only intended to be a fail-safe in case the Regions REST API goes
* offline and a user downloads and installs OBA Android during that period
* (i.e., local OBA servers are available, but Regions REST API failure would block initial
* execution of the app). This avoids a potential central point of failure for OBA
* Android installations on devices in multiple regions.
*
* @return list of regions retrieved from the regions file in app resources
*/
public static ArrayList<ObaRegion> getRegionsFromResources(Context context) {
final Uri.Builder builder = new Uri.Builder();
builder.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE);
builder.authority(context.getPackageName());
builder.path(Integer.toString(R.raw.regions_v3));
ObaRegionsResponse response = ObaRegionsRequest.newRequest(context, builder.build()).call();
return new ArrayList<ObaRegion>(Arrays.asList(response.getRegions()));
}
/**
* Retrieves hard-coded region information from the build flavor defined in build.gradle.
* If a fixed region is defined in a build flavor, it does not allow region roaming.
*
* @return hard-coded region information from the build flavor defined in build.gradle
*/
public static ObaRegion getRegionFromBuildFlavor() {
final int regionId = Integer.MAX_VALUE; // This doesn't get used, but needs to be positive
ObaRegionElement.Bounds[] boundsArray = new ObaRegionElement.Bounds[1];
ObaRegionElement.Bounds bounds = new ObaRegionElement.Bounds(
BuildConfig.FIXED_REGION_BOUNDS_LAT, BuildConfig.FIXED_REGION_BOUNDS_LON,
BuildConfig.FIXED_REGION_BOUNDS_LAT_SPAN, BuildConfig.FIXED_REGION_BOUNDS_LON_SPAN);
boundsArray[0] = bounds;
ObaRegionElement.Open311Server[] open311Array = new ObaRegionElement.Open311Server[1];
ObaRegionElement.Open311Server open311Server;
if (BuildConfig.FIXED_REGION_OPEN311_BASE_URL != null) {
open311Server = new ObaRegionElement.Open311Server (
BuildConfig.FIXED_REGION_OPEN311_JURISDICTION_ID,
BuildConfig.FIXED_REGION_OPEN311_API_KEY,
BuildConfig.FIXED_REGION_OPEN311_BASE_URL);
open311Array[0] = open311Server;
} else {
open311Array = null;
}
ObaRegionElement region = new ObaRegionElement(regionId,
BuildConfig.FIXED_REGION_NAME, true,
BuildConfig.FIXED_REGION_OBA_BASE_URL, BuildConfig.FIXED_REGION_SIRI_BASE_URL,
boundsArray, open311Array, BuildConfig.FIXED_REGION_LANG,
BuildConfig.FIXED_REGION_CONTACT_EMAIL,
BuildConfig.FIXED_REGION_SUPPORTS_OBA_DISCOVERY_APIS,
BuildConfig.FIXED_REGION_SUPPORTS_OBA_REALTIME_APIS,
BuildConfig.FIXED_REGION_SUPPORTS_SIRI_REALTIME_APIS,
BuildConfig.FIXED_REGION_TWITTER_URL, false,
BuildConfig.FIXED_REGION_STOP_INFO_URL,
BuildConfig.FIXED_REGION_OTP_BASE_URL,
BuildConfig.FIXED_REGION_OTP_CONTACT_EMAIL);
return region;
}
//
// Saving
//
public synchronized static void saveToProvider(Context context, List<ObaRegion> regions) {
// Delete all the existing regions
ContentResolver cr = context.getContentResolver();
cr.delete(ObaContract.Regions.CONTENT_URI, null, null);
// Should be a no-op?
cr.delete(ObaContract.RegionBounds.CONTENT_URI, null, null);
// Delete all existing open311 endpoints
cr.delete(ObaContract.RegionOpen311Servers.CONTENT_URI, null, null);
for (ObaRegion region : regions) {
if (!isRegionUsable(region)) {
Log.d(TAG, "Skipping insert of '" + region.getName() + "' to provider...");
continue;
}
cr.insert(ObaContract.Regions.CONTENT_URI, toContentValues(region));
Log.d(TAG, "Saved region '" + region.getName() + "' to provider");
long regionId = region.getId();
// Bulk insert the bounds
ObaRegion.Bounds[] bounds = region.getBounds();
if (bounds != null) {
ContentValues[] values = new ContentValues[bounds.length];
for (int i = 0; i < bounds.length; ++i) {
values[i] = toContentValues(regionId, bounds[i]);
}
cr.bulkInsert(ObaContract.RegionBounds.CONTENT_URI, values);
}
ObaRegion.Open311Server[] open311Servers = region.getOpen311Servers();
if (open311Servers != null) {
ContentValues[] values = new ContentValues[open311Servers.length];
for (int i = 0; i < open311Servers.length; ++i) {
values[i] = toContentValues(regionId, open311Servers[i]);
}
cr.bulkInsert(ObaContract.RegionOpen311Servers.CONTENT_URI, values);
}
}
}
private static ContentValues toContentValues(ObaRegion region) {
ContentValues values = new ContentValues();
values.put(ObaContract.Regions._ID, region.getId());
values.put(ObaContract.Regions.NAME, region.getName());
String obaUrl = region.getObaBaseUrl();
values.put(ObaContract.Regions.OBA_BASE_URL, obaUrl != null ? obaUrl : "");
String siriUrl = region.getSiriBaseUrl();
values.put(ObaContract.Regions.SIRI_BASE_URL, siriUrl != null ? siriUrl : "");
values.put(ObaContract.Regions.LANGUAGE, region.getLanguage());
values.put(ObaContract.Regions.CONTACT_EMAIL, region.getContactEmail());
values.put(ObaContract.Regions.SUPPORTS_OBA_DISCOVERY,
region.getSupportsObaDiscoveryApis() ? 1 : 0);
values.put(ObaContract.Regions.SUPPORTS_OBA_REALTIME,
region.getSupportsObaRealtimeApis() ? 1 : 0);
values.put(ObaContract.Regions.SUPPORTS_SIRI_REALTIME,
region.getSupportsSiriRealtimeApis() ? 1 : 0);
values.put(ObaContract.Regions.TWITTER_URL, region.getTwitterUrl());
values.put(ObaContract.Regions.EXPERIMENTAL, region.getExperimental());
values.put(ObaContract.Regions.STOP_INFO_URL, region.getStopInfoUrl());
values.put(ObaContract.Regions.OTP_BASE_URL, region.getOtpBaseUrl());
values.put(ObaContract.Regions.OTP_CONTACT_EMAIL, region.getOtpContactEmail());
return values;
}
private static ContentValues toContentValues(long region, ObaRegion.Bounds bounds) {
ContentValues values = new ContentValues();
values.put(ObaContract.RegionBounds.REGION_ID, region);
values.put(ObaContract.RegionBounds.LATITUDE, bounds.getLat());
values.put(ObaContract.RegionBounds.LONGITUDE, bounds.getLon());
values.put(ObaContract.RegionBounds.LAT_SPAN, bounds.getLatSpan());
values.put(ObaContract.RegionBounds.LON_SPAN, bounds.getLonSpan());
return values;
}
private static ContentValues toContentValues(long region, ObaRegion.Open311Server open311Server) {
ContentValues values = new ContentValues();
values.put(ObaContract.RegionOpen311Servers.REGION_ID, region);
values.put(ObaContract.RegionOpen311Servers.BASE_URL, open311Server.getBaseUrl());
values.put(ObaContract.RegionOpen311Servers.JURISDICTION, open311Server.getJuridisctionId());
values.put(ObaContract.RegionOpen311Servers.API_KEY, open311Server.getApiKey());
return values;
}
}