Fishing Regulations & Master Angler System Documentation
Overview
This system provides comprehensive support for state fishing regulations, zone management, and Master Angler programs. It enables location-based regulation lookup, annual regulation updates, and automatic Master Angler qualification checking.
Research Findings
Geospatial Data Availability: ✅ YES, coordinates exist!
- NOAA Fisheries provides GIS datasets for fishing zones
- State DNR agencies provide geospatial boundary data
- Fishing zones can be defined using:
- Simple: Center point + radius (circular zones)
- Moderate: Bounding box (rectangular zones)
- Complex: Polygon coordinates (irregular boundaries)
Master Angler Programs: Programs exist in multiple states:
- Michigan: Master Angler Program (certificate + patch)
- Wisconsin: Trophy Fish Program
- Minnesota: Master Angler Program
- Other states: Various catch recognition programs
Core Entities
State
Represents a state/province with fishing regulations.
Key Features:
- State information (name, abbreviation, country)
- DNR/Wildlife agency contact information
- Master Angler program information
- Regulation year tracking
- Links to zones and regulations
FishingZone
Represents a fishing zone/area within a state.
Boundary Types (supports all three):
- Circular:
CenterLatitude,CenterLongitude,RadiusMeters - Rectangular:
NorthBound,SouthBound,EastBound,WestBound - Polygon: Collection of
ZoneBoundarypoints
Features:
- Zone hierarchy (parent/child zones)
- Water body information
- Regulation year tracking
- Multiple boundary methods for flexibility
ZoneBoundary
Stores polygon coordinate points for complex zone boundaries.
Usage: When zones have irregular shapes, store multiple coordinate points in order to form a polygon.
FishingRegulation
Represents species-specific regulations for a zone.
Key Features:
- Bag limits (daily, possession)
- Size limits (minimum, maximum, slot limits)
- Season dates (via
RegulationSeason) - Gear restrictions
- Special rules
RegulationSeason
Date ranges when regulations apply.
Features:
- Start/end dates
- Time restrictions (optional)
- Day of week restrictions
- Multiple seasons per regulation
MasterAnglerProgram
State Master Angler or catch recognition program.
Features:
- Program information and requirements
- Recognition types (certificate, patch, pin, letter, trophy)
- Program year tracking
- Links to species requirements
MasterAnglerSpecies
Size requirements for species in Master Angler program.
Features:
- Minimum length/weight requirements
- Length OR weight qualification options
- Program year tracking
MasterAnglerSubmission
User's submission to Master Angler program.
Features:
- Links to catch log entry
- Submission status tracking
- Official program response tracking
- Recognition received tracking
Usage Examples
Finding Regulations for User's Location
public async Task<FishingRegulation?> GetRegulationForLocation(
double latitude,
double longitude,
int stateId,
int? speciesId,
DateOnly date)
{
// Find zone user is in
var zone = await FindZoneForLocation(latitude, longitude, stateId);
if (zone == null)
return null;
// Find regulation for zone and species
var regulation = await context.FishingRegulations
.Include(r => r.Seasons)
.FirstOrDefaultAsync(r =>
r.StateId == stateId
&& r.ZoneId == zone.Id
&& (r.FishSpeciesId == speciesId || r.FishSpeciesId == null)
&& r.RegulationYear == DateTime.Now.Year
&& r.IsActive);
// Check if date is within any active season
if (regulation != null)
{
var activeSeason = regulation.Seasons
.FirstOrDefault(s =>
s.IsActive
&& date >= s.StartDate
&& date <= s.EndDate);
if (activeSeason == null)
return null; // Season closed
}
return regulation;
}
private async Task<FishingZone?> FindZoneForLocation(
double latitude,
double longitude,
int stateId)
{
// Try circular zones first
var circularZone = await context.FishingZones
.Where(z => z.StateId == stateId
&& z.CenterLatitude.HasValue
&& z.CenterLongitude.HasValue
&& z.RadiusMeters.HasValue)
.ToListAsync();
foreach (var zone in circularZone)
{
var distance = CalculateDistance(
latitude, longitude,
zone.CenterLatitude.Value,
zone.CenterLongitude.Value);
if (distance <= zone.RadiusMeters.Value)
return zone;
}
// Try bounding box
var boundingBoxZone = await context.FishingZones
.FirstOrDefaultAsync(z =>
z.StateId == stateId
&& z.NorthBound.HasValue
&& z.SouthBound.HasValue
&& z.EastBound.HasValue
&& z.WestBound.HasValue
&& latitude >= z.SouthBound.Value
&& latitude <= z.NorthBound.Value
&& longitude >= z.WestBound.Value
&& longitude <= z.EastBound.Value);
if (boundingBoxZone != null)
return boundingBoxZone;
// Try polygon zones (point-in-polygon algorithm)
var polygonZones = await context.FishingZones
.Include(z => z.Boundaries.OrderBy(b => b.PointOrder))
.Where(z => z.StateId == stateId && z.Boundaries.Any())
.ToListAsync();
foreach (var zone in polygonZones)
{
if (IsPointInPolygon(latitude, longitude, zone.Boundaries))
return zone;
}
return null;
}
private bool IsPointInPolygon(double lat, double lon, ICollection<ZoneBoundary> boundaries)
{
// Ray casting algorithm for point-in-polygon
var points = boundaries.OrderBy(b => b.PointOrder).ToList();
bool inside = false;
for (int i = 0, j = points.Count - 1; i < points.Count; j = i++)
{
if (((points[i].Latitude > lat) != (points[j].Latitude > lat)) &&
(lon < (points[j].Longitude - points[i].Longitude) *
(lat - points[i].Latitude) / (points[j].Latitude - points[i].Latitude) +
points[i].Longitude))
{
inside = !inside;
}
}
return inside;
}
Checking Master Angler Qualification
public async Task<bool> CheckMasterAnglerQualification(
Guid userId,
Guid catchLogEntryId,
int stateId)
{
var catchDetail = await context.CatchDetails
.Include(cd => cd.FishingLogEntry)
.FirstOrDefaultAsync(cd => cd.FishingLogEntryId == catchLogEntryId);
if (catchDetail == null)
return false;
var program = await context.MasterAnglerPrograms
.Include(p => p.Species)
.FirstOrDefaultAsync(p =>
p.StateId == stateId
&& p.ProgramYear == DateTime.Now.Year
&& p.IsActive
&& p.IsAcceptingSubmissions);
if (program == null)
return false;
var speciesRequirement = program.Species
.FirstOrDefault(s =>
s.FishSpeciesId == catchDetail.FishSpeciesId
&& s.IsActive);
if (speciesRequirement == null)
return false;
// Check length requirement
bool qualifiesByLength = catchDetail.LengthInches.HasValue &&
catchDetail.LengthInches.Value >= speciesRequirement.MinimumLengthInches;
// Check weight requirement (if applicable)
bool qualifiesByWeight = catchDetail.WeightLbs.HasValue &&
speciesRequirement.MinimumWeightLbs.HasValue &&
catchDetail.WeightLbs.Value >= speciesRequirement.MinimumWeightLbs.Value;
// Check if length OR weight qualifies
if (speciesRequirement.LengthOrWeight)
return qualifiesByLength || qualifiesByWeight;
// Both must qualify
return qualifiesByLength &&
(!speciesRequirement.MinimumWeightLbs.HasValue || qualifiesByWeight);
}
Annual Regulation Update Process
public async Task UpdateRegulationsForYear(int stateId, int newYear)
{
// Archive old regulations
var oldRegulations = await context.FishingRegulations
.Where(r => r.StateId == stateId && r.RegulationYear < newYear)
.ToListAsync();
foreach (var reg in oldRegulations)
{
reg.IsActive = false;
reg.ExpirationDate = DateTime.UtcNow;
}
// Copy structure for new year (can be modified)
var newRegulations = oldRegulations.Select(r => new FishingRegulation
{
StateId = r.StateId,
ZoneId = r.ZoneId,
FishSpeciesId = r.FishSpeciesId,
RegulationYear = newYear,
DailyBagLimit = r.DailyBagLimit,
MinimumLengthInches = r.MinimumLengthInches,
// ... copy other fields
IsActive = true,
EffectiveDate = new DateTime(newYear, 1, 1)
}).ToList();
context.FishingRegulations.AddRange(newRegulations);
await context.SaveChangesAsync();
// Update state's current regulation year
var state = await context.States.FindAsync(stateId);
if (state != null)
{
state.CurrentRegulationYear = newYear;
state.LastRegulationUpdate = DateTime.UtcNow;
await context.SaveChangesAsync();
}
}
Annual Update Strategy
- Regulation Year Tracking: Each regulation has a
RegulationYearfield - Bulk Update: Copy previous year's regulations, modify as needed
- Archive Old Data: Set
IsActive = falsefor old regulations - Zone Boundaries: Usually don't change, but can be updated if needed
- Master Angler Programs: Update
ProgramYearand species requirements
Data Sources
- NOAA Fisheries: GIS data for federal waters
- State DNR Websites: Official regulation PDFs and web pages
- State GIS Portals: Geospatial boundary data
- Master Angler Program Websites: Official program requirements
Benefits
- Location-Based Lookup: Automatically show regulations based on GPS location
- Annual Updates: Easy to update regulations each year
- Master Angler Integration: Automatic qualification checking
- Compliance: Help users stay compliant with regulations
- Geospatial Flexibility: Supports simple to complex zone boundaries